diff --git a/.backportrc.json b/.backportrc.json index f463ddda99cae..5348a52e43bdc 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -45,5 +45,6 @@ }, "autoMerge": true, "autoMergeMethod": "squash", - "backportBinary": "node scripts/backport" + "backportBinary": "node scripts/backport", + "prDescription": "{defaultPrDescription}\n\n" } diff --git a/.buildkite/scripts/steps/functional/performance_playwright.sh b/.buildkite/scripts/steps/functional/performance_playwright.sh index fbbd028f96adf..a5271c58f6c4c 100644 --- a/.buildkite/scripts/steps/functional/performance_playwright.sh +++ b/.buildkite/scripts/steps/functional/performance_playwright.sh @@ -33,7 +33,11 @@ unset ELASTIC_APM_GLOBAL_LABELS export TEST_ES_URL=http://elastic:changeme@localhost:9200 export TEST_ES_DISABLE_STARTUP=true -sleep 120 +# Pings the es server every seconds 2 mins until it is status is green +curl --retry 120 \ + --retry-delay 1 \ + --retry-all-errors \ + -I -XGET "${TEST_ES_URL}/_cluster/health?wait_for_nodes=>=1&wait_for_status=yellow" journeys=("login" "ecommerce_dashboard" "flight_dashboard" "web_logs_dashboard" "promotion_tracking_dashboard" "many_fields_discover") diff --git a/.eslintrc.js b/.eslintrc.js index 6a8b460c79e41..dbb76d34ca0e5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1762,23 +1762,12 @@ module.exports = { }, }, - { - files: ['packages/kbn-type-summarizer/**/*.ts'], - rules: { - 'no-bitwise': 'off', - }, - }, - /** * Prettier disables all conflicting rules, listing as last override so it takes precedence */ { files: ['**/*'], - rules: { - ...require('eslint-config-prettier').rules, - ...require('eslint-config-prettier/react').rules, - ...require('eslint-config-prettier/@typescript-eslint').rules, - }, + rules: require('eslint-config-prettier').rules, }, /** * Enterprise Search Prettier override diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f62d103124558..3caf39ff52f3d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,8 +5,8 @@ # The #CC# prefix delineates Code Coverage, # used for the 'team' designator within Kibana Stats -# Virtual teams -/x-pack/plugins/rule_registry/ @elastic/rac @elastic/response-ops +# Alerting commmunal ownership +/x-pack/plugins/rule_registry/ @elastic/security-detections-response @elastic/actionable-observability @elastic/response-ops # Data Discovery /src/plugins/discover/ @elastic/kibana-data-discovery @@ -240,7 +240,7 @@ /packages/kbn-plugin-discovery/ @elastic/kibana-operations /packages/kbn-pm/ @elastic/kibana-operations /packages/kbn-test/ @elastic/kibana-operations -/packages/kbn-type-summarizer/ @elastic/kibana-operations +/packages/kbn-type-summarizer*/ @elastic/kibana-operations /packages/kbn-ui-shared-deps-npm/ @elastic/kibana-operations /packages/kbn-ui-shared-deps-src/ @elastic/kibana-operations /packages/kbn-utils/ @elastic/kibana-operations @@ -595,6 +595,7 @@ x-pack/plugins/security_solution/public/kubernetes @elastic/awp-platform # Cloud Security Posture /x-pack/plugins/cloud_security_posture/ @elastic/kibana-cloud-security-posture +/x-pack/plugins/security_solution/public/cloud_security_posture @elastic/kibana-cloud-security-posture # Design (at the bottom for specificity of SASS files) **/*.scss @elastic/kibana-design diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index d55634c958289..8f02d3224b66c 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -16,14 +16,14 @@ jobs: ) steps: - name: Backport Action - uses: sqren/backport-github-action@v8.5.2 + uses: sqren/backport-github-action@v8.8.0 with: github_token: ${{secrets.KIBANAMACHINE_TOKEN}} - name: Info log if: ${{ success() }} run: cat /home/runner/.backport/backport.info.log - + - name: Debug log if: ${{ failure() }} - run: cat /home/runner/.backport/backport.debug.log + run: cat /home/runner/.backport/backport.debug.log diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 0a924b79e2fd5..209ab2eec0757 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github summary: API docs for the actions plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index d59a7ca9a6773..9bed41c6f2749 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github summary: API docs for the advancedSettings plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 683238001eb31..6901b1758b170 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github summary: API docs for the aiops plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index bae41322adabc..98d41ffcb8e50 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github summary: API docs for the alerting plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index 7ccefb7faba3a..0a43a0aadbeb0 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -790,7 +790,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/groups/main_statistics\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/ux/page-load-distribution\" | \"GET /internal/apm/ux/page-load-distribution/breakdown\" | \"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\" | \"POST /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}/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}/infrastructure_attributes_for_logs\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"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/traces/find\" | \"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/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"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/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\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"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_apm_policies\" | \"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/backends/operations\" | \"GET /internal/apm/backends/charts/distribution\" | \"GET /internal/apm/backends/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\"" + "\"POST /internal/apm/data_view/static\" | \"GET /internal/apm/data_view/dynamic\" | \"GET /internal/apm/environments\" | \"GET /internal/apm/services/{serviceName}/errors/groups/main_statistics\" | \"POST /internal/apm/services/{serviceName}/errors/groups/detailed_statistics\" | \"GET /internal/apm/services/{serviceName}/errors/{groupId}\" | \"GET /internal/apm/services/{serviceName}/errors/distribution\" | \"POST /internal/apm/latency/overall_distribution/transactions\" | \"GET /internal/apm/services/{serviceName}/metrics/charts\" | \"GET /internal/apm/observability_overview\" | \"GET /internal/apm/observability_overview/has_data\" | \"GET /internal/apm/ux/page-load-distribution\" | \"GET /internal/apm/ux/page-load-distribution/breakdown\" | \"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\" | \"POST /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}/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}/infrastructure_attributes_for_logs\" | \"GET /internal/apm/services/{serviceName}/anomaly_charts\" | \"GET /internal/apm/sorted_and_filtered_services\" | \"GET /internal/apm/service-groups\" | \"GET /internal/apm/service-group\" | \"POST /internal/apm/service-group\" | \"DELETE /internal/apm/service-group\" | \"GET /internal/apm/service-group/services\" | \"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/traces/find\" | \"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/services/{serviceName}/transactions/charts/coldstart_rate\" | \"GET /internal/apm/services/{serviceName}/transactions/charts/coldstart_rate_by_transaction_name\" | \"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/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\" | \"POST /internal/apm/settings/anomaly-detection/update_to_v3\" | \"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_apm_policies\" | \"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/fleet/java_agent_versions\" | \"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/backends/operations\" | \"GET /internal/apm/backends/charts/distribution\" | \"GET /internal/apm/backends/operations/spans\" | \"GET /internal/apm/correlations/field_candidates/transactions\" | \"POST /internal/apm/correlations/field_stats/transactions\" | \"GET /internal/apm/correlations/field_value_stats/transactions\" | \"POST /internal/apm/correlations/field_value_pairs/transactions\" | \"POST /internal/apm/correlations/significant_correlations/transactions\" | \"POST /internal/apm/correlations/p_values/transactions\" | \"GET /internal/apm/fallback_to_transactions\" | \"GET /internal/apm/has_data\" | \"GET /internal/apm/event_metadata/{processorEvent}/{id}\" | \"GET /internal/apm/agent_keys\" | \"GET /internal/apm/agent_keys/privileges\" | \"POST /internal/apm/api_key/invalidate\" | \"POST /api/apm/agent_keys\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/parents\" | \"GET /internal/apm/traces/{traceId}/span_links/{spanId}/children\" | \"GET /internal/apm/services/{serviceName}/infrastructure_attributes\" | \"GET /internal/apm/debug-telemetry\" | \"GET /internal/apm/time_range_metadata\"" ], "path": "x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts", "deprecated": false, @@ -2020,6 +2020,18 @@ "Node", "; }[]; }, ", "APMRouteCreateOptions", + ">; \"GET /internal/apm/fleet/java_agent_versions\": ", + "ServerRoute", + "<\"GET /internal/apm/fleet/java_agent_versions\", undefined, ", + { + "pluginId": "apm", + "scope": "server", + "docId": "kibApmPluginApi", + "section": "def-server.APMRouteHandlerResources", + "text": "APMRouteHandlerResources" + }, + ", { versions: string[] | undefined; }, ", + "APMRouteCreateOptions", ">; \"POST /internal/apm/fleet/cloud_apm_package_policy\": ", "ServerRoute", "<\"POST /internal/apm/fleet/cloud_apm_package_policy\", undefined, ", diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index 350d27e94c7e6..746399ca1d92a 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github summary: API docs for the apm plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 49c393c29ecec..bc8484650710b 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github summary: API docs for the banners plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index c304d33271310..e0199df1a2525 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github summary: API docs for the bfetch plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index dc0fb09c91d0e..470e7aaa5a936 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github summary: API docs for the canvas plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/cases.devdocs.json b/api_docs/cases.devdocs.json index 4f6616f8208df..ddc787b6c1725 100644 --- a/api_docs/cases.devdocs.json +++ b/api_docs/cases.devdocs.json @@ -490,7 +490,7 @@ "signature": [ "{ features?: Partial<", "CasesContextFeatures", - "> | undefined; basePath?: string | undefined; onClose?: (() => void) | undefined; owner: string[]; userCanCrud: boolean; releasePhase?: ", + "> | undefined; basePath?: string | undefined; onClose?: (() => void) | undefined; owner: string[]; permissions: { all: boolean; read: boolean; }; releasePhase?: ", "ReleasePhase", " | undefined; hiddenStatuses?: ", "CaseStatusWithAllStatus", @@ -518,7 +518,7 @@ "signature": [ "{ features?: Partial<", "CasesContextFeatures", - "> | undefined; basePath?: string | undefined; owner: string[]; userCanCrud: boolean; onComponentInitialized?: (() => void) | undefined; actionsNavigation?: ", + "> | undefined; basePath?: string | undefined; owner: string[]; permissions: { all: boolean; read: boolean; }; onComponentInitialized?: (() => void) | undefined; actionsNavigation?: ", "CasesNavigation", " | undefined; ruleDetailsNavigation?: ", "CasesNavigation", @@ -552,7 +552,7 @@ "signature": [ "{ features?: Partial<", "CasesContextFeatures", - "> | undefined; basePath?: string | undefined; onClose?: (() => void) | undefined; owner: string[]; userCanCrud: boolean; releasePhase?: ", + "> | undefined; basePath?: string | undefined; onClose?: (() => void) | undefined; owner: string[]; permissions: { all: boolean; read: boolean; }; releasePhase?: ", "ReleasePhase", " | undefined; afterCaseCreated?: ((theCase: ", { @@ -596,7 +596,7 @@ "signature": [ "{ features?: Partial<", "CasesContextFeatures", - "> | undefined; basePath?: string | undefined; owner: string[]; userCanCrud: boolean; releasePhase?: ", + "> | undefined; basePath?: string | undefined; owner: string[]; permissions: { all: boolean; read: boolean; }; releasePhase?: ", "ReleasePhase", " | undefined; maxCasesToShow: number; }" ], diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 67eb668c7d77c..2f592ca6a795a 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github summary: API docs for the cases plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 25996ac5e773a..646566c4d4ecc 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github summary: API docs for the charts plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 549a4295ae5d5..c64d726bdd1ef 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github summary: API docs for the cloud plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index e9573b9b887a6..8dc3145701ad7 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github summary: API docs for the cloudSecurityPosture plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/console.mdx b/api_docs/console.mdx index acd0582278c34..2d40351812d6a 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github summary: API docs for the console plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 22090e8c1d9a5..c9b0d12fd2b2a 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github summary: API docs for the controls plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index f39f42b2dd3bd..ac2f8ce234328 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -649,7 +649,7 @@ "tags": [], "label": "AnalyticsClient", "description": [ - "\r\nAnalytics client's public APIs" + "\nAnalytics client's public APIs" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -661,7 +661,7 @@ "tags": [], "label": "reportEvent", "description": [ - "\r\nReports a telemetry event." + "\nReports a telemetry event." ], "signature": [ ">(eventType: string, eventData: EventTypeData) => void" @@ -711,7 +711,7 @@ "tags": [], "label": "registerEventType", "description": [ - "\r\nRegisters the event type that will be emitted via the reportEvent API." + "\nRegisters the event type that will be emitted via the reportEvent API." ], "signature": [ "(eventTypeOps: ", @@ -748,7 +748,7 @@ "tags": [], "label": "registerShipper", "description": [ - "\r\nSet up the shipper that will be used to report the telemetry events." + "\nSet up the shipper that will be used to report the telemetry events." ], "signature": [ "(contextProviderOpts: ", @@ -895,7 +895,7 @@ "tags": [], "label": "removeContextProvider", "description": [ - "\r\nRemoves the context provider and stop enriching the events from its context." + "\nRemoves the context provider and stop enriching the events from its context." ], "signature": [ "(contextProviderName: string) => void" @@ -929,7 +929,7 @@ "tags": [], "label": "telemetryCounter$", "description": [ - "\r\nObservable to emit the stats of the processed events." + "\nObservable to emit the stats of the processed events." ], "signature": [ "Observable", @@ -947,7 +947,7 @@ "tags": [], "label": "shutdown", "description": [ - "\r\nStops the client." + "\nStops the client." ], "signature": [ "() => void" @@ -1126,7 +1126,7 @@ "tags": [], "label": "ContextProviderOpts", "description": [ - "\r\nDefinition of a context provider" + "\nDefinition of a context provider" ], "signature": [ "ContextProviderOpts", @@ -1142,7 +1142,7 @@ "tags": [], "label": "name", "description": [ - "\r\nThe name of the provider." + "\nThe name of the provider." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false @@ -1154,7 +1154,7 @@ "tags": [], "label": "context$", "description": [ - "\r\nObservable that emits the custom context." + "\nObservable that emits the custom context." ], "signature": [ "Observable", @@ -1170,7 +1170,7 @@ "tags": [], "label": "schema", "description": [ - "\r\nSchema declaring and documenting the expected output in the context$\r\n" + "\nSchema declaring and documenting the expected output in the context$\n" ], "signature": [ "{ [Key in keyof Required]: ", @@ -1709,7 +1709,7 @@ "tags": [], "label": "CoreTheme", "description": [ - "\r\nContains all the required information to apply Kibana's theme at the various levels it can be used.\r\n" + "\nContains all the required information to apply Kibana's theme at the various levels it can be used.\n" ], "path": "node_modules/@types/kbn__core-theme-browser/index.d.ts", "deprecated": false, @@ -2026,51 +2026,48 @@ }, { "parentPluginId": "core", - "id": "def-public.Event_2", + "id": "def-public.Event", "type": "Interface", "tags": [], - "label": "Event_2", + "label": "Event", "description": [ - "\r\nDefinition of the full event structure" - ], - "signature": [ - "Event" + "\nDefinition of the full event structure" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, "children": [ { "parentPluginId": "core", - "id": "def-public.Event_2.timestamp", + "id": "def-public.Event.timestamp", "type": "string", "tags": [], "label": "timestamp", "description": [ - "\r\nThe time the event was generated in ISO format." + "\nThe time the event was generated in ISO format." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false }, { "parentPluginId": "core", - "id": "def-public.Event_2.event_type", + "id": "def-public.Event.event_type", "type": "string", "tags": [], "label": "event_type", "description": [ - "\r\nThe event type." + "\nThe event type." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false }, { "parentPluginId": "core", - "id": "def-public.Event_2.properties", + "id": "def-public.Event.properties", "type": "Object", "tags": [], "label": "properties", "description": [ - "\r\nThe specific properties of the event type." + "\nThe specific properties of the event type." ], "signature": [ "{ [x: string]: unknown; }" @@ -2080,12 +2077,12 @@ }, { "parentPluginId": "core", - "id": "def-public.Event_2.context", + "id": "def-public.Event.context", "type": "Object", "tags": [], "label": "context", "description": [ - "\r\nThe {@link EventContext} enriched during the processing pipeline." + "\nThe {@link EventContext} enriched during the processing pipeline." ], "signature": [ "EventContext" @@ -2103,7 +2100,7 @@ "tags": [], "label": "EventContext", "description": [ - "\r\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." + "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -2115,7 +2112,7 @@ "tags": [], "label": "cluster_uuid", "description": [ - "\r\nThe UUID of the cluster" + "\nThe UUID of the cluster" ], "signature": [ "string | undefined" @@ -2130,7 +2127,7 @@ "tags": [], "label": "cluster_name", "description": [ - "\r\nThe name of the cluster." + "\nThe name of the cluster." ], "signature": [ "string | undefined" @@ -2145,7 +2142,7 @@ "tags": [], "label": "license_id", "description": [ - "\r\nThe license ID." + "\nThe license ID." ], "signature": [ "string | undefined" @@ -2160,7 +2157,7 @@ "tags": [], "label": "userId", "description": [ - "\r\nThe unique user ID." + "\nThe unique user ID." ], "signature": [ "string | undefined" @@ -2175,7 +2172,7 @@ "tags": [], "label": "cloudId", "description": [ - "\r\nThe Cloud ID." + "\nThe Cloud ID." ], "signature": [ "string | undefined" @@ -2190,7 +2187,7 @@ "tags": [], "label": "isElasticCloudUser", "description": [ - "\r\n`true` if the user is logged in via the Elastic Cloud authentication provider." + "\n`true` if the user is logged in via the Elastic Cloud authentication provider." ], "signature": [ "boolean | undefined" @@ -2205,7 +2202,7 @@ "tags": [], "label": "version", "description": [ - "\r\nThe product's version." + "\nThe product's version." ], "signature": [ "string | undefined" @@ -2220,7 +2217,7 @@ "tags": [], "label": "pageName", "description": [ - "\r\nThe name of the current page." + "\nThe name of the current page." ], "signature": [ "string | undefined" @@ -2235,7 +2232,7 @@ "tags": [], "label": "applicationId", "description": [ - "\r\nThe current application ID." + "\nThe current application ID." ], "signature": [ "string | undefined" @@ -2250,7 +2247,7 @@ "tags": [], "label": "entityId", "description": [ - "\r\nThe current entity ID (dashboard ID, visualization ID, etc.)." + "\nThe current entity ID (dashboard ID, visualization ID, etc.)." ], "signature": [ "string | undefined" @@ -2265,7 +2262,7 @@ "tags": [], "label": "[key: string]: unknown", "description": [ - "\r\nAdditional keys are allowed." + "\nAdditional keys are allowed." ], "signature": [ "[key: string]: unknown" @@ -2283,7 +2280,7 @@ "tags": [], "label": "EventTypeOpts", "description": [ - "\r\nDefinition of an Event Type." + "\nDefinition of an Event Type." ], "signature": [ "EventTypeOpts", @@ -2299,7 +2296,7 @@ "tags": [], "label": "eventType", "description": [ - "\r\nThe event type's unique name." + "\nThe event type's unique name." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false @@ -2311,7 +2308,7 @@ "tags": [], "label": "schema", "description": [ - "\r\nSchema declaring and documenting the expected structure of this event type.\r\n" + "\nSchema declaring and documenting the expected structure of this event type.\n" ], "signature": [ "{ [Key in keyof Required]: ", @@ -2331,7 +2328,7 @@ "tags": [], "label": "ExecutionContextSetup", "description": [ - "\r\nKibana execution context.\r\nUsed to provide execution context to Elasticsearch, reporting, performance monitoring, etc." + "\nKibana execution context.\nUsed to provide execution context to Elasticsearch, reporting, performance monitoring, etc." ], "path": "node_modules/@types/kbn__core-execution-context-browser/index.d.ts", "deprecated": false, @@ -2343,7 +2340,7 @@ "tags": [], "label": "context$", "description": [ - "\r\nThe current context observable" + "\nThe current context observable" ], "signature": [ "Observable", @@ -2361,7 +2358,7 @@ "tags": [], "label": "set", "description": [ - "\r\nSet the current top level context" + "\nSet the current top level context" ], "signature": [ "(c$: ", @@ -2395,7 +2392,7 @@ "tags": [], "label": "get", "description": [ - "\r\nGet the current top level context" + "\nGet the current top level context" ], "signature": [ "() => ", @@ -2413,7 +2410,7 @@ "tags": [], "label": "clear", "description": [ - "\r\nclears the context" + "\nclears the context" ], "signature": [ "() => void" @@ -2430,10 +2427,11 @@ "tags": [], "label": "getAsLabels", "description": [ - "\r\nreturns apm labels" + "\nreturns apm labels" ], "signature": [ - "() => Labels" + "() => ", + "Labels" ], "path": "node_modules/@types/kbn__core-execution-context-browser/index.d.ts", "deprecated": false, @@ -2447,7 +2445,7 @@ "tags": [], "label": "withGlobalContext", "description": [ - "\r\nmerges the current top level context with the specific event context" + "\nmerges the current top level context with the specific event context" ], "signature": [ "(context?: ", @@ -2486,7 +2484,7 @@ "tags": [], "label": "FatalErrorInfo", "description": [ - "\r\nRepresents the `message` and `stack` of a fatal Error\r\n" + "\nRepresents the `message` and `stack` of a fatal Error\n" ], "path": "node_modules/@types/kbn__core-fatal-errors-browser/index.d.ts", "deprecated": false, @@ -2524,7 +2522,7 @@ "tags": [], "label": "FatalErrorsSetup", "description": [ - "\r\nFatalErrors stop the Kibana Public Core and displays a fatal error screen\r\nwith details about the Kibana build and the error.\r\n" + "\nFatalErrors stop the Kibana Public Core and displays a fatal error screen\nwith details about the Kibana build and the error.\n" ], "path": "node_modules/@types/kbn__core-fatal-errors-browser/index.d.ts", "deprecated": false, @@ -2536,7 +2534,7 @@ "tags": [], "label": "add", "description": [ - "\r\nAdd a new fatal error. This will stop the Kibana Public Core and display\r\na fatal error screen with details about the Kibana build and the error.\r\n" + "\nAdd a new fatal error. This will stop the Kibana Public Core and display\na fatal error screen with details about the Kibana build and the error.\n" ], "signature": [ "(error: string | Error, source?: string | undefined) => never" @@ -2586,7 +2584,7 @@ "tags": [], "label": "get$", "description": [ - "\r\nAn Observable that will emit whenever a fatal error is added with `add()`" + "\nAn Observable that will emit whenever a fatal error is added with `add()`" ], "signature": [ "() => ", @@ -2610,7 +2608,7 @@ "tags": [], "label": "I18nStart", "description": [ - "\r\nI18nStart.Context is required by any localizable React component from \\@kbn/i18n and \\@elastic/eui packages\r\nand is supposed to be used as the topmost component for any i18n-compatible React tree.\r\n" + "\nI18nStart.Context is required by any localizable React component from \\@kbn/i18n and \\@elastic/eui packages\nand is supposed to be used as the topmost component for any i18n-compatible React tree.\n" ], "path": "node_modules/@types/kbn__core-i18n-browser/index.d.ts", "deprecated": false, @@ -2622,7 +2620,7 @@ "tags": [], "label": "Context", "description": [ - "\r\nReact Context provider required as the topmost component for any i18n-compatible React tree." + "\nReact Context provider required as the topmost component for any i18n-compatible React tree." ], "signature": [ "({ children }: { children: React.ReactNode; }) => JSX.Element" @@ -2670,7 +2668,7 @@ "description": [ "\nA policy describing whether access to an external destination is allowed." ], - "path": "src/core/server/external_url/external_url_config.ts", + "path": "src/core/server/http/external_url/external_url_config.ts", "deprecated": false, "children": [ { @@ -2682,7 +2680,7 @@ "description": [ "\nIndicates if this policy allows or denies access to the described destination." ], - "path": "src/core/server/external_url/external_url_config.ts", + "path": "src/core/server/http/external_url/external_url_config.ts", "deprecated": false }, { @@ -2697,7 +2695,7 @@ "signature": [ "string | undefined" ], - "path": "src/core/server/external_url/external_url_config.ts", + "path": "src/core/server/http/external_url/external_url_config.ts", "deprecated": false }, { @@ -2712,7 +2710,7 @@ "signature": [ "string | undefined" ], - "path": "src/core/server/external_url/external_url_config.ts", + "path": "src/core/server/http/external_url/external_url_config.ts", "deprecated": false } ], @@ -2725,7 +2723,7 @@ "tags": [], "label": "IShipper", "description": [ - "\r\nBasic structure of a Shipper" + "\nBasic structure of a Shipper" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -2737,7 +2735,7 @@ "tags": [], "label": "reportEvents", "description": [ - "\r\nAdapts and ships the event to the persisting/analytics solution." + "\nAdapts and ships the event to the persisting/analytics solution." ], "signature": [ "(events: ", @@ -2774,7 +2772,7 @@ "tags": [], "label": "optIn", "description": [ - "\r\nStops/restarts the shipping mechanism based on the value of isOptedIn" + "\nStops/restarts the shipping mechanism based on the value of isOptedIn" ], "signature": [ "(isOptedIn: boolean) => void" @@ -2808,7 +2806,7 @@ "tags": [], "label": "extendContext", "description": [ - "\r\nPerform any necessary calls to the persisting/analytics solution to set the event's context." + "\nPerform any necessary calls to the persisting/analytics solution to set the event's context." ], "signature": [ "((newContext: ", @@ -2844,7 +2842,7 @@ "tags": [], "label": "telemetryCounter$", "description": [ - "\r\nObservable to emit the stats of the processed events." + "\nObservable to emit the stats of the processed events." ], "signature": [ "Observable", @@ -2862,7 +2860,7 @@ "tags": [], "label": "shutdown", "description": [ - "\r\nShutdown the shipper." + "\nShutdown the shipper." ], "signature": [ "() => void" @@ -3604,7 +3602,7 @@ "tags": [], "label": "OptInConfig", "description": [ - "\r\n" + "\n" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -3616,7 +3614,7 @@ "tags": [], "label": "global", "description": [ - "\r\nControls the global enabled/disabled behaviour of the client and shippers." + "\nControls the global enabled/disabled behaviour of the client and shippers." ], "signature": [ "OptInConfigPerType" @@ -3631,7 +3629,7 @@ "tags": [], "label": "event_types", "description": [ - "\r\nControls if an event type should be disabled for a specific type of shipper." + "\nControls if an event type should be disabled for a specific type of shipper." ], "signature": [ "Record>(eventType: string, eventData: EventTypeData) => void" @@ -9119,7 +9013,7 @@ "tags": [], "label": "registerEventType", "description": [ - "\r\nRegisters the event type that will be emitted via the reportEvent API." + "\nRegisters the event type that will be emitted via the reportEvent API." ], "signature": [ "(eventTypeOps: ", @@ -9156,7 +9050,7 @@ "tags": [], "label": "registerShipper", "description": [ - "\r\nSet up the shipper that will be used to report the telemetry events." + "\nSet up the shipper that will be used to report the telemetry events." ], "signature": [ "(contextProviderOpts: ", @@ -9303,7 +9197,7 @@ "tags": [], "label": "removeContextProvider", "description": [ - "\r\nRemoves the context provider and stop enriching the events from its context." + "\nRemoves the context provider and stop enriching the events from its context." ], "signature": [ "(contextProviderName: string) => void" @@ -9337,7 +9231,7 @@ "tags": [], "label": "telemetryCounter$", "description": [ - "\r\nObservable to emit the stats of the processed events." + "\nObservable to emit the stats of the processed events." ], "signature": [ "Observable", @@ -9355,7 +9249,7 @@ "tags": [], "label": "shutdown", "description": [ - "\r\nStops the client." + "\nStops the client." ], "signature": [ "() => void" @@ -10009,7 +9903,7 @@ "tags": [], "label": "ConfigDeprecationContext", "description": [ - "\r\nDeprecation context provided to {@link ConfigDeprecation | config deprecations}\r\n" + "\nDeprecation context provided to {@link ConfigDeprecation | config deprecations}\n" ], "path": "node_modules/@types/kbn__config/index.d.ts", "deprecated": false, @@ -10116,7 +10010,7 @@ "tags": [], "label": "ConfigDeprecationFactory", "description": [ - "\r\nProvides helpers to generates the most commonly used {@link ConfigDeprecation}\r\nwhen invoking a {@link ConfigDeprecationProvider}.\r\n\r\nSee methods documentation for more detailed examples.\r\n" + "\nProvides helpers to generates the most commonly used {@link ConfigDeprecation}\nwhen invoking a {@link ConfigDeprecationProvider}.\n\nSee methods documentation for more detailed examples.\n" ], "path": "node_modules/@types/kbn__config/index.d.ts", "deprecated": false, @@ -10128,10 +10022,12 @@ "tags": [], "label": "deprecate", "description": [ - "\r\nDeprecate a configuration property from inside a plugin's configuration path.\r\nWill log a deprecation warning if the deprecatedKey was found.\r\n" + "\nDeprecate a configuration property from inside a plugin's configuration path.\nWill log a deprecation warning if the deprecatedKey was found.\n" ], "signature": [ - "(deprecatedKey: string, removeBy: string, details: FactoryConfigDeprecationDetails) => ", + "(deprecatedKey: string, removeBy: string, details: ", + "FactoryConfigDeprecationDetails", + ") => ", "ConfigDeprecation" ], "path": "node_modules/@types/kbn__config/index.d.ts", @@ -10189,10 +10085,12 @@ "tags": [], "label": "deprecateFromRoot", "description": [ - "\r\nDeprecate a configuration property from the root configuration.\r\nWill log a deprecation warning if the deprecatedKey was found.\r\n\r\nThis should be only used when deprecating properties from different configuration's path.\r\nTo deprecate properties from inside a plugin's configuration, use 'deprecate' instead.\r\n" + "\nDeprecate a configuration property from the root configuration.\nWill log a deprecation warning if the deprecatedKey was found.\n\nThis should be only used when deprecating properties from different configuration's path.\nTo deprecate properties from inside a plugin's configuration, use 'deprecate' instead.\n" ], "signature": [ - "(deprecatedKey: string, removeBy: string, details: FactoryConfigDeprecationDetails) => ", + "(deprecatedKey: string, removeBy: string, details: ", + "FactoryConfigDeprecationDetails", + ") => ", "ConfigDeprecation" ], "path": "node_modules/@types/kbn__config/index.d.ts", @@ -10250,10 +10148,12 @@ "tags": [], "label": "rename", "description": [ - "\r\nRename a configuration property from inside a plugin's configuration path.\r\nWill log a deprecation warning if the oldKey was found and deprecation applied.\r\n" + "\nRename a configuration property from inside a plugin's configuration path.\nWill log a deprecation warning if the oldKey was found and deprecation applied.\n" ], "signature": [ - "(oldKey: string, newKey: string, details: FactoryConfigDeprecationDetails) => ", + "(oldKey: string, newKey: string, details: ", + "FactoryConfigDeprecationDetails", + ") => ", "ConfigDeprecation" ], "path": "node_modules/@types/kbn__config/index.d.ts", @@ -10311,10 +10211,12 @@ "tags": [], "label": "renameFromRoot", "description": [ - "\r\nRename a configuration property from the root configuration.\r\nWill log a deprecation warning if the oldKey was found and deprecation applied.\r\n\r\nThis should be only used when renaming properties from different configuration's path.\r\nTo rename properties from inside a plugin's configuration, use 'rename' instead.\r\n" + "\nRename a configuration property from the root configuration.\nWill log a deprecation warning if the oldKey was found and deprecation applied.\n\nThis should be only used when renaming properties from different configuration's path.\nTo rename properties from inside a plugin's configuration, use 'rename' instead.\n" ], "signature": [ - "(oldKey: string, newKey: string, details: FactoryConfigDeprecationDetails) => ", + "(oldKey: string, newKey: string, details: ", + "FactoryConfigDeprecationDetails", + ") => ", "ConfigDeprecation" ], "path": "node_modules/@types/kbn__config/index.d.ts", @@ -10372,10 +10274,12 @@ "tags": [], "label": "unused", "description": [ - "\r\nRemove a configuration property from inside a plugin's configuration path.\r\nWill log a deprecation warning if the unused key was found and deprecation applied.\r\n" + "\nRemove a configuration property from inside a plugin's configuration path.\nWill log a deprecation warning if the unused key was found and deprecation applied.\n" ], "signature": [ - "(unusedKey: string, details: FactoryConfigDeprecationDetails) => ", + "(unusedKey: string, details: ", + "FactoryConfigDeprecationDetails", + ") => ", "ConfigDeprecation" ], "path": "node_modules/@types/kbn__config/index.d.ts", @@ -10419,10 +10323,12 @@ "tags": [], "label": "unusedFromRoot", "description": [ - "\r\nRemove a configuration property from the root configuration.\r\nWill log a deprecation warning if the unused key was found and deprecation applied.\r\n\r\nThis should be only used when removing properties from outside of a plugin's configuration.\r\nTo remove properties from inside a plugin's configuration, use 'unused' instead.\r\n" + "\nRemove a configuration property from the root configuration.\nWill log a deprecation warning if the unused key was found and deprecation applied.\n\nThis should be only used when removing properties from outside of a plugin's configuration.\nTo remove properties from inside a plugin's configuration, use 'unused' instead.\n" ], "signature": [ - "(unusedKey: string, details: FactoryConfigDeprecationDetails) => ", + "(unusedKey: string, details: ", + "FactoryConfigDeprecationDetails", + ") => ", "ConfigDeprecation" ], "path": "node_modules/@types/kbn__config/index.d.ts", @@ -10469,7 +10375,7 @@ "tags": [], "label": "ContextProviderOpts", "description": [ - "\r\nDefinition of a context provider" + "\nDefinition of a context provider" ], "signature": [ "ContextProviderOpts", @@ -10485,7 +10391,7 @@ "tags": [], "label": "name", "description": [ - "\r\nThe name of the provider." + "\nThe name of the provider." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false @@ -10497,7 +10403,7 @@ "tags": [], "label": "context$", "description": [ - "\r\nObservable that emits the custom context." + "\nObservable that emits the custom context." ], "signature": [ "Observable", @@ -10513,7 +10419,7 @@ "tags": [], "label": "schema", "description": [ - "\r\nSchema declaring and documenting the expected output in the context$\r\n" + "\nSchema declaring and documenting the expected output in the context$\n" ], "signature": [ "{ [Key in keyof Required]: ", @@ -10606,7 +10512,16 @@ "docId": "kibCoreHttpPluginApi", "section": "def-server.HttpServicePreboot", "text": "HttpServicePreboot" - } + }, + "<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + ">" ], "path": "src/core/server/index.ts", "deprecated": false @@ -10644,7 +10559,7 @@ "description": [ "\nThe `core` context provided to route handler.\n\nProvides the following clients and services:\n - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client\n which uses the credentials of the incoming request\n - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing\n all the registered types.\n - {@link IScopedClusterClient | elasticsearch.client} - Elasticsearch\n data client which uses the credentials of the incoming request\n - {@link IUiSettingsClient | uiSettings.client} - uiSettings client\n which uses the credentials of the incoming request" ], - "path": "src/core/server/index.ts", + "path": "src/core/server/core_route_handler_context.ts", "deprecated": false, "children": [ { @@ -10655,73 +10570,15 @@ "label": "savedObjects", "description": [], "signature": [ - "{ client: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - }, - "; typeRegistry: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.ISavedObjectTypeRegistry", - "text": "ISavedObjectTypeRegistry" - }, - "; getClient: (options?: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClientProviderOptions", - "text": "SavedObjectsClientProviderOptions" - }, - " | undefined) => ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - }, - "; getExporter: (client: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - }, - ") => ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.ISavedObjectsExporter", - "text": "ISavedObjectsExporter" - }, - "; getImporter: (client: ", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.SavedObjectsClientContract", - "text": "SavedObjectsClientContract" - }, - ") => ", { "pluginId": "core", "scope": "server", "docId": "kibCoreSavedObjectsPluginApi", - "section": "def-server.ISavedObjectsImporter", - "text": "ISavedObjectsImporter" - }, - "; }" + "section": "def-server.SavedObjectsRequestHandlerContext", + "text": "SavedObjectsRequestHandlerContext" + } ], - "path": "src/core/server/index.ts", + "path": "src/core/server/core_route_handler_context.ts", "deprecated": false }, { @@ -10732,17 +10589,15 @@ "label": "elasticsearch", "description": [], "signature": [ - "{ client: ", { "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.IScopedClusterClient", - "text": "IScopedClusterClient" - }, - "; }" + "section": "def-server.ElasticsearchRequestHandlerContext", + "text": "ElasticsearchRequestHandlerContext" + } ], - "path": "src/core/server/index.ts", + "path": "src/core/server/core_route_handler_context.ts", "deprecated": false }, { @@ -10753,17 +10608,15 @@ "label": "uiSettings", "description": [], "signature": [ - "{ client: ", { "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.IUiSettingsClient", - "text": "IUiSettingsClient" - }, - "; }" + "section": "def-server.UiSettingsRequestHandlerContext", + "text": "UiSettingsRequestHandlerContext" + } ], - "path": "src/core/server/index.ts", + "path": "src/core/server/core_route_handler_context.ts", "deprecated": false }, { @@ -10774,17 +10627,15 @@ "label": "deprecations", "description": [], "signature": [ - "{ client: ", { "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.DeprecationsClient", - "text": "DeprecationsClient" - }, - "; }" + "section": "def-server.DeprecationsRequestHandlerContext", + "text": "DeprecationsRequestHandlerContext" + } ], - "path": "src/core/server/index.ts", + "path": "src/core/server/core_route_handler_context.ts", "deprecated": false } ], @@ -10932,7 +10783,15 @@ "section": "def-server.HttpServiceSetup", "text": "HttpServiceSetup" }, - " & { resources: ", + "<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + "> & { resources: ", { "pluginId": "core", "scope": "server", @@ -11579,6 +11438,40 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.DeprecationsRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "DeprecationsRequestHandlerContext", + "description": [ + "\nCore's `deprecations` request handler context." + ], + "path": "src/core/server/deprecations/deprecations_route_handler_context.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.DeprecationsRequestHandlerContext.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.DeprecationsClient", + "text": "DeprecationsClient" + } + ], + "path": "src/core/server/deprecations/deprecations_route_handler_context.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.DeprecationsServiceSetup", @@ -11645,7 +11538,7 @@ "tags": [], "label": "DiscoveredPlugin", "description": [ - "\r\nSmall container object used to expose information about discovered plugins that may\r\nor may not have been started." + "\nSmall container object used to expose information about discovered plugins that may\nor may not have been started." ], "path": "node_modules/@types/kbn__core-base-common/index.d.ts", "deprecated": false, @@ -11657,7 +11550,7 @@ "tags": [], "label": "id", "description": [ - "\r\nIdentifier of the plugin." + "\nIdentifier of the plugin." ], "path": "node_modules/@types/kbn__core-base-common/index.d.ts", "deprecated": false @@ -11669,7 +11562,7 @@ "tags": [], "label": "configPath", "description": [ - "\r\nRoot configuration path used by the plugin, defaults to \"id\" in snake_case format." + "\nRoot configuration path used by the plugin, defaults to \"id\" in snake_case format." ], "signature": [ "string | string[]" @@ -11684,7 +11577,7 @@ "tags": [], "label": "type", "description": [ - "\r\nType of the plugin, defaults to `standard`." + "\nType of the plugin, defaults to `standard`." ], "signature": [ "PluginType" @@ -11699,7 +11592,7 @@ "tags": [], "label": "requiredPlugins", "description": [ - "\r\nAn optional list of the other plugins that **must be** installed and enabled\r\nfor this plugin to function properly." + "\nAn optional list of the other plugins that **must be** installed and enabled\nfor this plugin to function properly." ], "signature": [ "readonly string[]" @@ -11714,7 +11607,7 @@ "tags": [], "label": "optionalPlugins", "description": [ - "\r\nAn optional list of the other plugins that if installed and enabled **may be**\r\nleveraged by this plugin for some additional functionality but otherwise are\r\nnot required for this plugin to work properly." + "\nAn optional list of the other plugins that if installed and enabled **may be**\nleveraged by this plugin for some additional functionality but otherwise are\nnot required for this plugin to work properly." ], "signature": [ "readonly string[]" @@ -11729,7 +11622,7 @@ "tags": [], "label": "requiredBundles", "description": [ - "\r\nList of plugin ids that this plugin's UI code imports modules from that are\r\nnot in `requiredPlugins`.\r\n" + "\nList of plugin ids that this plugin's UI code imports modules from that are\nnot in `requiredPlugins`.\n" ], "signature": [ "readonly string[]" @@ -11744,7 +11637,7 @@ "tags": [], "label": "enabledOnAnonymousPages", "description": [ - "\r\nSpecifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when\r\nconfigured, etc.) Default is false." + "\nSpecifies whether this plugin - and its required dependencies - will be enabled for anonymous pages (login page, status page when\nconfigured, etc.) Default is false." ], "signature": [ "boolean | undefined" @@ -11849,6 +11742,40 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.ElasticsearchRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "ElasticsearchRequestHandlerContext", + "description": [ + "\nCore's `elasticsearch` request handler context." + ], + "path": "src/core/server/elasticsearch/elasticsearch_route_handler_context.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.ElasticsearchRequestHandlerContext.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCorePluginApi", + "section": "def-server.IScopedClusterClient", + "text": "IScopedClusterClient" + } + ], + "path": "src/core/server/elasticsearch/elasticsearch_route_handler_context.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.ElasticsearchServicePreboot", @@ -12247,51 +12174,48 @@ }, { "parentPluginId": "core", - "id": "def-server.Event_2", + "id": "def-server.Event", "type": "Interface", "tags": [], - "label": "Event_2", + "label": "Event", "description": [ - "\r\nDefinition of the full event structure" - ], - "signature": [ - "Event" + "\nDefinition of the full event structure" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, "children": [ { "parentPluginId": "core", - "id": "def-server.Event_2.timestamp", + "id": "def-server.Event.timestamp", "type": "string", "tags": [], "label": "timestamp", "description": [ - "\r\nThe time the event was generated in ISO format." + "\nThe time the event was generated in ISO format." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false }, { "parentPluginId": "core", - "id": "def-server.Event_2.event_type", + "id": "def-server.Event.event_type", "type": "string", "tags": [], "label": "event_type", "description": [ - "\r\nThe event type." + "\nThe event type." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false }, { "parentPluginId": "core", - "id": "def-server.Event_2.properties", + "id": "def-server.Event.properties", "type": "Object", "tags": [], "label": "properties", "description": [ - "\r\nThe specific properties of the event type." + "\nThe specific properties of the event type." ], "signature": [ "{ [x: string]: unknown; }" @@ -12301,12 +12225,12 @@ }, { "parentPluginId": "core", - "id": "def-server.Event_2.context", + "id": "def-server.Event.context", "type": "Object", "tags": [], "label": "context", "description": [ - "\r\nThe {@link EventContext} enriched during the processing pipeline." + "\nThe {@link EventContext} enriched during the processing pipeline." ], "signature": [ "EventContext" @@ -12324,7 +12248,7 @@ "tags": [], "label": "EventContext", "description": [ - "\r\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." + "\nDefinition of the context that can be appended to the events through the {@link IAnalyticsClient.registerContextProvider}." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -12336,7 +12260,7 @@ "tags": [], "label": "cluster_uuid", "description": [ - "\r\nThe UUID of the cluster" + "\nThe UUID of the cluster" ], "signature": [ "string | undefined" @@ -12351,7 +12275,7 @@ "tags": [], "label": "cluster_name", "description": [ - "\r\nThe name of the cluster." + "\nThe name of the cluster." ], "signature": [ "string | undefined" @@ -12366,7 +12290,7 @@ "tags": [], "label": "license_id", "description": [ - "\r\nThe license ID." + "\nThe license ID." ], "signature": [ "string | undefined" @@ -12381,7 +12305,7 @@ "tags": [], "label": "userId", "description": [ - "\r\nThe unique user ID." + "\nThe unique user ID." ], "signature": [ "string | undefined" @@ -12396,7 +12320,7 @@ "tags": [], "label": "cloudId", "description": [ - "\r\nThe Cloud ID." + "\nThe Cloud ID." ], "signature": [ "string | undefined" @@ -12411,7 +12335,7 @@ "tags": [], "label": "isElasticCloudUser", "description": [ - "\r\n`true` if the user is logged in via the Elastic Cloud authentication provider." + "\n`true` if the user is logged in via the Elastic Cloud authentication provider." ], "signature": [ "boolean | undefined" @@ -12426,7 +12350,7 @@ "tags": [], "label": "version", "description": [ - "\r\nThe product's version." + "\nThe product's version." ], "signature": [ "string | undefined" @@ -12441,7 +12365,7 @@ "tags": [], "label": "pageName", "description": [ - "\r\nThe name of the current page." + "\nThe name of the current page." ], "signature": [ "string | undefined" @@ -12456,7 +12380,7 @@ "tags": [], "label": "applicationId", "description": [ - "\r\nThe current application ID." + "\nThe current application ID." ], "signature": [ "string | undefined" @@ -12471,7 +12395,7 @@ "tags": [], "label": "entityId", "description": [ - "\r\nThe current entity ID (dashboard ID, visualization ID, etc.)." + "\nThe current entity ID (dashboard ID, visualization ID, etc.)." ], "signature": [ "string | undefined" @@ -12486,7 +12410,7 @@ "tags": [], "label": "[key: string]: unknown", "description": [ - "\r\nAdditional keys are allowed." + "\nAdditional keys are allowed." ], "signature": [ "[key: string]: unknown" @@ -12504,7 +12428,7 @@ "tags": [], "label": "EventTypeOpts", "description": [ - "\r\nDefinition of an Event Type." + "\nDefinition of an Event Type." ], "signature": [ "EventTypeOpts", @@ -12520,7 +12444,7 @@ "tags": [], "label": "eventType", "description": [ - "\r\nThe event type's unique name." + "\nThe event type's unique name." ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false @@ -12532,7 +12456,7 @@ "tags": [], "label": "schema", "description": [ - "\r\nSchema declaring and documenting the expected structure of this event type.\r\n" + "\nSchema declaring and documenting the expected structure of this event type.\n" ], "signature": [ "{ [Key in keyof Required]: ", @@ -12562,7 +12486,7 @@ "tags": [], "label": "withContext", "description": [ - "\r\nKeeps track of execution context while the passed function is executed.\r\nData are carried over all async operations spawned by the passed function.\r\nThe nested calls stack the registered context on top of each other." + "\nKeeps track of execution context while the passed function is executed.\nData are carried over all async operations spawned by the passed function.\nThe nested calls stack the registered context on top of each other." ], "signature": [ "(context: ", @@ -14906,8 +14830,8 @@ "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" }, ", ContextName extends keyof Context>(pluginOpaqueId: symbol, contextName: ContextName, provider: ", { @@ -14996,7 +14920,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, @@ -15430,7 +15354,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, @@ -15644,77 +15568,14 @@ }, { "parentPluginId": "core", - "id": "def-server.ICspConfig", + "id": "def-server.ICustomClusterClient", "type": "Interface", "tags": [], - "label": "ICspConfig", + "label": "ICustomClusterClient", "description": [ - "\nCSP configuration for use in Kibana." + "\nSee {@link IClusterClient}\n" ], - "path": "src/core/server/csp/csp_config.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "core", - "id": "def-server.ICspConfig.strict", - "type": "boolean", - "tags": [], - "label": "strict", - "description": [ - "\nSpecify whether browsers that do not support CSP should be\nable to use Kibana. Use `true` to block and `false` to allow." - ], - "path": "src/core/server/csp/csp_config.ts", - "deprecated": false - }, - { - "parentPluginId": "core", - "id": "def-server.ICspConfig.warnLegacyBrowsers", - "type": "boolean", - "tags": [], - "label": "warnLegacyBrowsers", - "description": [ - "\nSpecify whether users with legacy browsers should be warned\nabout their lack of Kibana security compliance." - ], - "path": "src/core/server/csp/csp_config.ts", - "deprecated": false - }, - { - "parentPluginId": "core", - "id": "def-server.ICspConfig.disableEmbedding", - "type": "boolean", - "tags": [], - "label": "disableEmbedding", - "description": [ - "\nWhether or not embedding (using iframes) should be allowed by the CSP. If embedding is disabled, a restrictive 'frame-ancestors' rule will be added to the default CSP rules." - ], - "path": "src/core/server/csp/csp_config.ts", - "deprecated": false - }, - { - "parentPluginId": "core", - "id": "def-server.ICspConfig.header", - "type": "string", - "tags": [], - "label": "header", - "description": [ - "\nThe CSP rules in a formatted directives string for use\nin a `Content-Security-Policy` header." - ], - "path": "src/core/server/csp/csp_config.ts", - "deprecated": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "core", - "id": "def-server.ICustomClusterClient", - "type": "Interface", - "tags": [], - "label": "ICustomClusterClient", - "description": [ - "\nSee {@link IClusterClient}\n" - ], - "signature": [ + "signature": [ { "pluginId": "core", "scope": "server", @@ -15799,100 +15660,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "core", - "id": "def-server.IExternalUrlConfig", - "type": "Interface", - "tags": [], - "label": "IExternalUrlConfig", - "description": [ - "\nExternal Url configuration for use in Kibana." - ], - "path": "src/core/server/external_url/external_url_config.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "core", - "id": "def-server.IExternalUrlConfig.policy", - "type": "Array", - "tags": [], - "label": "policy", - "description": [ - "\nA set of policies describing which external urls are allowed." - ], - "signature": [ - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.IExternalUrlPolicy", - "text": "IExternalUrlPolicy" - }, - "[]" - ], - "path": "src/core/server/external_url/external_url_config.ts", - "deprecated": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "core", - "id": "def-server.IExternalUrlPolicy", - "type": "Interface", - "tags": [], - "label": "IExternalUrlPolicy", - "description": [ - "\nA policy describing whether access to an external destination is allowed." - ], - "path": "src/core/server/external_url/external_url_config.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "core", - "id": "def-server.IExternalUrlPolicy.allow", - "type": "boolean", - "tags": [], - "label": "allow", - "description": [ - "\nIndicates if this policy allows or denies access to the described destination." - ], - "path": "src/core/server/external_url/external_url_config.ts", - "deprecated": false - }, - { - "parentPluginId": "core", - "id": "def-server.IExternalUrlPolicy.host", - "type": "string", - "tags": [], - "label": "host", - "description": [ - "\nOptional host describing the external destination.\nMay be combined with `protocol`.\n" - ], - "signature": [ - "string | undefined" - ], - "path": "src/core/server/external_url/external_url_config.ts", - "deprecated": false - }, - { - "parentPluginId": "core", - "id": "def-server.IExternalUrlPolicy.protocol", - "type": "string", - "tags": [], - "label": "protocol", - "description": [ - "\nOptional protocol describing the external destination.\nMay be combined with `host`.\n" - ], - "signature": [ - "string | undefined" - ], - "path": "src/core/server/external_url/external_url_config.ts", - "deprecated": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "core", "id": "def-server.IntervalHistogram", @@ -18447,7 +18214,7 @@ "tags": [], "label": "IShipper", "description": [ - "\r\nBasic structure of a Shipper" + "\nBasic structure of a Shipper" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -18459,7 +18226,7 @@ "tags": [], "label": "reportEvents", "description": [ - "\r\nAdapts and ships the event to the persisting/analytics solution." + "\nAdapts and ships the event to the persisting/analytics solution." ], "signature": [ "(events: ", @@ -18496,7 +18263,7 @@ "tags": [], "label": "optIn", "description": [ - "\r\nStops/restarts the shipping mechanism based on the value of isOptedIn" + "\nStops/restarts the shipping mechanism based on the value of isOptedIn" ], "signature": [ "(isOptedIn: boolean) => void" @@ -18530,7 +18297,7 @@ "tags": [], "label": "extendContext", "description": [ - "\r\nPerform any necessary calls to the persisting/analytics solution to set the event's context." + "\nPerform any necessary calls to the persisting/analytics solution to set the event's context." ], "signature": [ "((newContext: ", @@ -18566,7 +18333,7 @@ "tags": [], "label": "telemetryCounter$", "description": [ - "\r\nObservable to emit the stats of the processed events." + "\nObservable to emit the stats of the processed events." ], "signature": [ "Observable", @@ -18584,7 +18351,7 @@ "tags": [], "label": "shutdown", "description": [ - "\r\nShutdown the shipper." + "\nShutdown the shipper." ], "signature": [ "() => void" @@ -18912,7 +18679,7 @@ "tags": [], "label": "Logger", "description": [ - "\r\nLogger exposes all the necessary methods to log any type of information and\r\nthis is the interface used by the logging consumers including plugins.\r\n" + "\nLogger exposes all the necessary methods to log any type of information and\nthis is the interface used by the logging consumers including plugins.\n" ], "path": "node_modules/@types/kbn__logging/index.d.ts", "deprecated": false, @@ -18924,7 +18691,7 @@ "tags": [], "label": "trace", "description": [ - "\r\nLog messages at the most detailed log level\r\n" + "\nLog messages at the most detailed log level\n" ], "signature": [ " ", @@ -19284,7 +19051,7 @@ "tags": [], "label": "LoggerConfigType", "description": [ - "\r\nDescribes the configuration of a given logger.\r\n" + "\nDescribes the configuration of a given logger.\n" ], "path": "node_modules/@types/kbn__core-logging-server/index.d.ts", "deprecated": false, @@ -19335,7 +19102,7 @@ "tags": [], "label": "LoggerContextConfigInput", "description": [ - "\r\nInput used to configure logging dynamically using {@link LoggingServiceSetup.configure}" + "\nInput used to configure logging dynamically using {@link LoggingServiceSetup.configure}" ], "path": "node_modules/@types/kbn__core-logging-server/index.d.ts", "deprecated": false, @@ -19381,7 +19148,7 @@ "tags": [], "label": "LoggerFactory", "description": [ - "\r\nThe single purpose of `LoggerFactory` interface is to define a way to\r\nretrieve a context-based logger instance.\r\n" + "\nThe single purpose of `LoggerFactory` interface is to define a way to\nretrieve a context-based logger instance.\n" ], "path": "node_modules/@types/kbn__logging/index.d.ts", "deprecated": false, @@ -19393,7 +19160,7 @@ "tags": [], "label": "get", "description": [ - "\r\nReturns a `Logger` instance for the specified context.\r\n" + "\nReturns a `Logger` instance for the specified context.\n" ], "signature": [ "(...contextParts: string[]) => ", @@ -19409,7 +19176,7 @@ "tags": [], "label": "contextParts", "description": [ - "- Parts of the context to return logger for. For example\r\nget('plugins', 'pid') will return a logger for the `plugins.pid` context." + "- Parts of the context to return logger for. For example\nget('plugins', 'pid') will return a logger for the `plugins.pid` context." ], "signature": [ "string[]" @@ -19431,7 +19198,7 @@ "tags": [], "label": "LoggingServiceSetup", "description": [ - "\r\nProvides APIs to plugins for customizing the plugin's logger." + "\nProvides APIs to plugins for customizing the plugin's logger." ], "path": "node_modules/@types/kbn__core-logging-server/index.d.ts", "deprecated": false, @@ -19443,7 +19210,7 @@ "tags": [], "label": "configure", "description": [ - "\r\nCustomizes the logging config for the plugin's context.\r\n" + "\nCustomizes the logging config for the plugin's context.\n" ], "signature": [ "(config$: ", @@ -19539,7 +19306,7 @@ "tags": [], "label": "NodeInfo", "description": [ - "\r\nContains information about how this Kibana process has been configured.\r\n" + "\nContains information about how this Kibana process has been configured.\n" ], "path": "node_modules/@types/kbn__core-node-server/index.d.ts", "deprecated": false, @@ -19569,7 +19336,7 @@ "tags": [], "label": "NodeRoles", "description": [ - "\r\nThe Kibana process can be run in dedicated \"modes\" via `node.roles`.\r\nThis configuration is then exposed to plugins via `NodeRoles`,\r\nwhich is available on the `PluginInitializerContext`.\r\n\r\nThe node roles can be used by plugins to adjust their behavior based\r\non the way the Kibana process has been configured.\r\n" + "\nThe Kibana process can be run in dedicated \"modes\" via `node.roles`.\nThis configuration is then exposed to plugins via `NodeRoles`,\nwhich is available on the `PluginInitializerContext`.\n\nThe node roles can be used by plugins to adjust their behavior based\non the way the Kibana process has been configured.\n" ], "path": "node_modules/@types/kbn__core-node-server/index.d.ts", "deprecated": false, @@ -19581,7 +19348,7 @@ "tags": [], "label": "backgroundTasks", "description": [ - "\r\nThe backgroundTasks role includes operations which don't involve\r\nresponding to incoming http traffic from the UI." + "\nThe backgroundTasks role includes operations which don't involve\nresponding to incoming http traffic from the UI." ], "path": "node_modules/@types/kbn__core-node-server/index.d.ts", "deprecated": false @@ -19593,7 +19360,7 @@ "tags": [], "label": "ui", "description": [ - "\r\nThe ui role covers any operations that need to occur in order\r\nto handle http traffic from the browser." + "\nThe ui role covers any operations that need to occur in order\nto handle http traffic from the browser." ], "path": "node_modules/@types/kbn__core-node-server/index.d.ts", "deprecated": false @@ -20130,7 +19897,7 @@ "tags": [], "label": "OptInConfig", "description": [ - "\r\n" + "\n" ], "path": "node_modules/@types/kbn__analytics-client/index.d.ts", "deprecated": false, @@ -20142,7 +19909,7 @@ "tags": [], "label": "global", "description": [ - "\r\nControls the global enabled/disabled behaviour of the client and shippers." + "\nControls the global enabled/disabled behaviour of the client and shippers." ], "signature": [ "OptInConfigPerType" @@ -20157,7 +19924,7 @@ "tags": [], "label": "event_types", "description": [ - "\r\nControls if an event type should be disabled for a specific type of shipper." + "\nControls if an event type should be disabled for a specific type of shipper." ], "signature": [ "Record, fromPath: string, addDeprecation: ", @@ -24413,7 +24216,7 @@ "tags": [], "label": "ConfigDeprecationProvider", "description": [ - "\r\nA provider that should returns a list of {@link ConfigDeprecation}.\r\n\r\nSee {@link ConfigDeprecationFactory} for more usage examples.\r\n" + "\nA provider that should returns a list of {@link ConfigDeprecation}.\n\nSee {@link ConfigDeprecationFactory} for more usage examples.\n" ], "signature": [ "(factory: ", @@ -24462,7 +24265,9 @@ "type": "Type", "tags": [], "label": "CustomRequestHandlerContext", - "description": [], + "description": [ + "\nMixin allowing plugins to define their own request handler contexts.\n" + ], "signature": [ { "pluginId": "core", @@ -24526,10 +24331,83 @@ "tags": [], "label": "Ecs", "description": [ - "\r\nRepresents the full ECS schema.\r\n" + "\nRepresents the full ECS schema.\n" ], "signature": [ - "EcsBase & EcsTracing & { ecs: EcsField; agent?: EcsAgent | undefined; as?: EcsAutonomousSystem | undefined; client?: EcsClient | undefined; cloud?: EcsCloud | undefined; container?: EcsContainer | undefined; data_stream?: EcsDataStream | undefined; destination?: EcsDestination | undefined; dns?: EcsDns | undefined; email?: EcsEmail | undefined; error?: EcsError | undefined; event?: EcsEvent | undefined; file?: EcsFile | undefined; group?: EcsGroup | undefined; host?: EcsHost | undefined; http?: EcsHttp | undefined; log?: EcsLog | undefined; network?: EcsNetwork | undefined; observer?: EcsObserver | undefined; orchestrator?: EcsOrchestrator | undefined; organization?: EcsOrganization | undefined; package?: EcsPackage | undefined; process?: EcsProcess | undefined; registry?: EcsRegistry | undefined; related?: EcsRelated | undefined; rule?: EcsRule | undefined; server?: EcsServer | undefined; service?: EcsService | undefined; source?: EcsSource | undefined; threat?: EcsThreat | undefined; tls?: EcsTls | undefined; url?: EcsUrl | undefined; user?: EcsUser | undefined; user_agent?: EcsUserAgent | undefined; vulnerability?: EcsVulnerability | undefined; }" + "EcsBase", + " & ", + "EcsTracing", + " & { ecs: ", + "EcsField", + "; agent?: ", + "EcsAgent", + " | undefined; as?: ", + "EcsAutonomousSystem", + " | undefined; client?: ", + "EcsClient", + " | undefined; cloud?: ", + "EcsCloud", + " | undefined; container?: ", + "EcsContainer", + " | undefined; data_stream?: ", + "EcsDataStream", + " | undefined; destination?: ", + "EcsDestination", + " | undefined; dns?: ", + "EcsDns", + " | undefined; email?: ", + "EcsEmail", + " | undefined; error?: ", + "EcsError", + " | undefined; event?: ", + "EcsEvent", + " | undefined; file?: ", + "EcsFile", + " | undefined; group?: ", + "EcsGroup", + " | undefined; host?: ", + "EcsHost", + " | undefined; http?: ", + "EcsHttp", + " | undefined; log?: ", + "EcsLog", + " | undefined; network?: ", + "EcsNetwork", + " | undefined; observer?: ", + "EcsObserver", + " | undefined; orchestrator?: ", + "EcsOrchestrator", + " | undefined; organization?: ", + "EcsOrganization", + " | undefined; package?: ", + "EcsPackage", + " | undefined; process?: ", + "EcsProcess", + " | undefined; registry?: ", + "EcsRegistry", + " | undefined; related?: ", + "EcsRelated", + " | undefined; rule?: ", + "EcsRule", + " | undefined; server?: ", + "EcsServer", + " | undefined; service?: ", + "EcsService", + " | undefined; source?: ", + "EcsSource", + " | undefined; threat?: ", + "EcsThreat", + " | undefined; tls?: ", + "EcsTls", + " | undefined; url?: ", + "EcsUrl", + " | undefined; user?: ", + "EcsUser", + " | undefined; user_agent?: ", + "EcsUserAgent", + " | undefined; vulnerability?: ", + "EcsVulnerability", + " | undefined; }" ], "path": "node_modules/@types/kbn__logging/index.d.ts", "deprecated": false, @@ -25826,7 +25704,7 @@ "tags": [], "label": "EventType", "description": [ - "\r\nEvent Type used for indexed structures. Only used to improve the readability of the types" + "\nEvent Type used for indexed structures. Only used to improve the readability of the types" ], "signature": [ "string" @@ -26733,10 +26611,76 @@ "tags": [], "label": "LogMeta", "description": [ - "\r\nRepresents the ECS schema with the following reserved keys excluded:\r\n- `ecs`\r\n- `@timestamp`\r\n- `message`\r\n- `log.level`\r\n- `log.logger`\r\n" + "\nRepresents the ECS schema with the following reserved keys excluded:\n- `ecs`\n- `@timestamp`\n- `message`\n- `log.level`\n- `log.logger`\n" ], "signature": [ - "Omit & EcsTracing & { agent?: EcsAgent | undefined; as?: EcsAutonomousSystem | undefined; client?: EcsClient | undefined; cloud?: EcsCloud | undefined; container?: EcsContainer | undefined; destination?: EcsDestination | undefined; dns?: EcsDns | undefined; error?: EcsError | undefined; event?: EcsEvent | undefined; file?: EcsFile | undefined; group?: EcsGroup | undefined; host?: EcsHost | undefined; http?: EcsHttp | undefined; log?: Omit | undefined; network?: EcsNetwork | undefined; observer?: EcsObserver | undefined; organization?: EcsOrganization | undefined; package?: EcsPackage | undefined; process?: EcsProcess | undefined; registry?: EcsRegistry | undefined; related?: EcsRelated | undefined; rule?: EcsRule | undefined; server?: EcsServer | undefined; service?: EcsService | undefined; source?: EcsSource | undefined; threat?: EcsThreat | undefined; tls?: EcsTls | undefined; url?: EcsUrl | undefined; user?: EcsUser | undefined; user_agent?: EcsUserAgent | undefined; vulnerability?: EcsVulnerability | undefined; }" + "Omit<", + "EcsBase", + ", \"message\" | \"@timestamp\"> & ", + "EcsTracing", + " & { agent?: ", + "EcsAgent", + " | undefined; as?: ", + "EcsAutonomousSystem", + " | undefined; client?: ", + "EcsClient", + " | undefined; cloud?: ", + "EcsCloud", + " | undefined; container?: ", + "EcsContainer", + " | undefined; destination?: ", + "EcsDestination", + " | undefined; dns?: ", + "EcsDns", + " | undefined; error?: ", + "EcsError", + " | undefined; event?: ", + "EcsEvent", + " | undefined; file?: ", + "EcsFile", + " | undefined; group?: ", + "EcsGroup", + " | undefined; host?: ", + "EcsHost", + " | undefined; http?: ", + "EcsHttp", + " | undefined; log?: Omit<", + "EcsLog", + ", \"logger\" | \"level\"> | undefined; network?: ", + "EcsNetwork", + " | undefined; observer?: ", + "EcsObserver", + " | undefined; organization?: ", + "EcsOrganization", + " | undefined; package?: ", + "EcsPackage", + " | undefined; process?: ", + "EcsProcess", + " | undefined; registry?: ", + "EcsRegistry", + " | undefined; related?: ", + "EcsRelated", + " | undefined; rule?: ", + "EcsRule", + " | undefined; server?: ", + "EcsServer", + " | undefined; service?: ", + "EcsService", + " | undefined; source?: ", + "EcsSource", + " | undefined; threat?: ", + "EcsThreat", + " | undefined; tls?: ", + "EcsTls", + " | undefined; url?: ", + "EcsUrl", + " | undefined; user?: ", + "EcsUser", + " | undefined; user_agent?: ", + "EcsUserAgent", + " | undefined; vulnerability?: ", + "EcsVulnerability", + " | undefined; }" ], "path": "node_modules/@types/kbn__logging/index.d.ts", "deprecated": false, @@ -26883,7 +26827,7 @@ "tags": [], "label": "PluginName", "description": [ - "\r\nDedicated type for plugin name/id that is supposed to make Map/Set/Arrays\r\nthat use it as a key or value more obvious.\r\n" + "\nDedicated type for plugin name/id that is supposed to make Map/Set/Arrays\nthat use it as a key or value more obvious.\n" ], "signature": [ "string" @@ -26906,6 +26850,141 @@ "deprecated": false, "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.PublicHttpServiceSetup", + "type": "Type", + "tags": [], + "label": "PublicHttpServiceSetup", + "description": [ + "\nPublic version of RequestHandlerContext, default-scoped to {@link RequestHandlerContext}\nSee [@link RequestHandlerContext}" + ], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.HttpServiceSetup", + "text": "HttpServiceSetup" + }, + "" + ], + "path": "src/core/server/index.ts", + "deprecated": false, + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-server.PublicRequestHandler", + "type": "Type", + "tags": [], + "label": "PublicRequestHandler", + "description": [ + "\nPublic version of RequestHandler, default-scoped to {@link RequestHandlerContext}\nSee [@link RequestHandler}" + ], + "signature": [ + "(context: Context, request: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + ", response: ResponseFactory) => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + " | Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.IKibanaResponse", + "text": "IKibanaResponse" + }, + ">" + ], + "path": "src/core/server/index.ts", + "deprecated": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "core", + "id": "def-server.PublicRequestHandler.$1", + "type": "Uncategorized", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "Context" + ], + "path": "src/core/server/http/router/router.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.PublicRequestHandler.$2", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "src/core/server/http/router/router.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.PublicRequestHandler.$3", + "type": "Uncategorized", + "tags": [], + "label": "response", + "description": [], + "signature": [ + "ResponseFactory" + ], + "path": "src/core/server/http/router/router.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-server.PublicRouter", + "type": "Type", + "tags": [], + "label": "PublicRouter", + "description": [ + "\nPublic version of IRouter, default-scoped to {@link RequestHandlerContext}\nSee [@link IRouter}" + ], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.IRouter", + "text": "IRouter" + }, + "" + ], + "path": "src/core/server/index.ts", + "deprecated": false, + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.PublicUiSettingsParams", @@ -27063,7 +27142,7 @@ "tags": [], "label": "TelemetryCounterType", "description": [ - "\r\nIndicates if the event contains data about succeeded, failed or dropped events:\r\n- enqueued: The event was accepted and will be sent to the shippers when they become available (and opt-in === true).\r\n- sent_to_shipper: The event was sent to at least one shipper.\r\n- succeeded: The event was successfully sent by the shipper.\r\n- failed: There was an error when processing/shipping the event. Refer to the Telemetry Counter's code for the reason.\r\n- dropped: The event was dropped from the queue. Refer to the Telemetry Counter's code for the reason." + "\nIndicates if the event contains data about succeeded, failed or dropped events:\n- enqueued: The event was accepted and will be sent to the shippers when they become available (and opt-in === true).\n- sent_to_shipper: The event was sent to at least one shipper.\n- succeeded: The event was successfully sent by the shipper.\n- failed: There was an error when processing/shipping the event. Refer to the Telemetry Counter's code for the reason.\n- dropped: The event was dropped from the queue. Refer to the Telemetry Counter's code for the reason." ], "signature": [ "\"failed\" | \"enqueued\" | \"sent_to_shipper\" | \"succeeded\" | \"dropped\"" diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 211be1d7b3378..779232a22bb90 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github summary: API docs for the core plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2525 | 15 | 914 | 28 | +| 2546 | 15 | 926 | 28 | ## Client diff --git a/api_docs/core_application.mdx b/api_docs/core_application.mdx index 1f96f2c27d492..6e01dbf920ea0 100644 --- a/api_docs/core_application.mdx +++ b/api_docs/core_application.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-application title: "core.application" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.application plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.application'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2525 | 15 | 914 | 28 | +| 2546 | 15 | 926 | 28 | ## Client diff --git a/api_docs/core_chrome.mdx b/api_docs/core_chrome.mdx index 013887c1e6bc9..e141c89aed64f 100644 --- a/api_docs/core_chrome.mdx +++ b/api_docs/core_chrome.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-chrome title: "core.chrome" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.chrome plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.chrome'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2525 | 15 | 914 | 28 | +| 2546 | 15 | 926 | 28 | ## Client diff --git a/api_docs/core_http.devdocs.json b/api_docs/core_http.devdocs.json index fc0ff622cc4aa..1eb2780206d34 100644 --- a/api_docs/core_http.devdocs.json +++ b/api_docs/core_http.devdocs.json @@ -2247,6 +2247,110 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.CspConfig", + "type": "Class", + "tags": [], + "label": "CspConfig", + "description": [ + "\nCSP configuration for use in Kibana." + ], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.CspConfig", + "text": "CspConfig" + }, + " implements ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.ICspConfig", + "text": "ICspConfig" + } + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.CspConfig.DEFAULT", + "type": "Object", + "tags": [], + "label": "DEFAULT", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.CspConfig", + "text": "CspConfig" + } + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.CspConfig.directives", + "type": "Object", + "tags": [], + "label": "#directives", + "description": [], + "signature": [ + "CspDirectives" + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.CspConfig.strict", + "type": "boolean", + "tags": [], + "label": "strict", + "description": [], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.CspConfig.warnLegacyBrowsers", + "type": "boolean", + "tags": [], + "label": "warnLegacyBrowsers", + "description": [], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.CspConfig.disableEmbedding", + "type": "boolean", + "tags": [], + "label": "disableEmbedding", + "description": [], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.CspConfig.header", + "type": "string", + "tags": [], + "label": "header", + "description": [], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.RouteValidationError", @@ -3112,6 +3216,16 @@ "description": [ "\nKibana HTTP Service provides an abstraction to work with the HTTP stack at the `preboot` stage. This functionality\nallows Kibana to serve user requests even before Kibana becomes fully operational. Only Core and `preboot` plugins\ncan define HTTP routes at this stage.\n" ], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.HttpServicePreboot", + "text": "HttpServicePreboot" + }, + "" + ], "path": "src/core/server/http/types.ts", "deprecated": false, "children": [ @@ -3125,7 +3239,7 @@ "\nProvides ability to acquire `preboot` {@link IRouter} instance for a particular top-level path and register handler\nfunctions for any number of nested routes.\n" ], "signature": [ - "(path: string, callback: (router: ", + "(path: string, callback: (router: ", { "pluginId": "core", "scope": "server", @@ -3133,15 +3247,7 @@ "section": "def-server.IRouter", "text": "IRouter" }, - "<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, - ">) => void) => void" + ") => void) => void" ], "path": "src/core/server/http/types.ts", "deprecated": false, @@ -3176,15 +3282,7 @@ "section": "def-server.IRouter", "text": "IRouter" }, - "<", - { - "pluginId": "core", - "scope": "server", - "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" - }, - ">) => void" + ") => void" ], "path": "src/core/server/http/types.ts", "deprecated": false, @@ -3260,6 +3358,16 @@ "description": [ "\nKibana HTTP Service provides own abstraction for work with HTTP stack.\nPlugins don't have direct access to `hapi` server and its primitives anymore. Moreover,\nplugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood.\nThis gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins.\nIf the HTTP Service lacks functionality you need, we are happy to discuss and support your needs.\n" ], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.HttpServiceSetup", + "text": "HttpServiceSetup" + }, + "" + ], "path": "src/core/server/http/types.ts", "deprecated": false, "children": [ @@ -3604,7 +3712,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCorePluginApi", + "docId": "kibCoreHttpPluginApi", "section": "def-server.ICspConfig", "text": "ICspConfig" } @@ -3622,23 +3730,7 @@ "\nProvides ability to declare a handler function for a particular path and HTTP request method.\n" ], "signature": [ - "() => ", + "() => ", { "pluginId": "core", "scope": "server", @@ -3663,15 +3755,7 @@ "\nRegister a context provider for a route handler." ], "signature": [ - ">(contextName: ContextName, provider: ", + ">(contextName: ContextName, provider: ", { "pluginId": "core", "scope": "server", @@ -3845,6 +3929,163 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.ICspConfig", + "type": "Interface", + "tags": [], + "label": "ICspConfig", + "description": [ + "\nCSP configuration for use in Kibana." + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.ICspConfig.strict", + "type": "boolean", + "tags": [], + "label": "strict", + "description": [ + "\nSpecify whether browsers that do not support CSP should be\nable to use Kibana. Use `true` to block and `false` to allow." + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.ICspConfig.warnLegacyBrowsers", + "type": "boolean", + "tags": [], + "label": "warnLegacyBrowsers", + "description": [ + "\nSpecify whether users with legacy browsers should be warned\nabout their lack of Kibana security compliance." + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.ICspConfig.disableEmbedding", + "type": "boolean", + "tags": [], + "label": "disableEmbedding", + "description": [ + "\nWhether or not embedding (using iframes) should be allowed by the CSP. If embedding is disabled, a restrictive 'frame-ancestors' rule will be added to the default CSP rules." + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.ICspConfig.header", + "type": "string", + "tags": [], + "label": "header", + "description": [ + "\nThe CSP rules in a formatted directives string for use\nin a `Content-Security-Policy` header." + ], + "path": "src/core/server/http/csp/csp_config.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-server.IExternalUrlConfig", + "type": "Interface", + "tags": [], + "label": "IExternalUrlConfig", + "description": [ + "\nExternal Url configuration for use in Kibana." + ], + "path": "src/core/server/http/external_url/external_url_config.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.IExternalUrlConfig.policy", + "type": "Array", + "tags": [], + "label": "policy", + "description": [ + "\nA set of policies describing which external urls are allowed." + ], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreHttpPluginApi", + "section": "def-server.IExternalUrlPolicy", + "text": "IExternalUrlPolicy" + }, + "[]" + ], + "path": "src/core/server/http/external_url/external_url_config.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-server.IExternalUrlPolicy", + "type": "Interface", + "tags": [], + "label": "IExternalUrlPolicy", + "description": [ + "\nA policy describing whether access to an external destination is allowed." + ], + "path": "src/core/server/http/external_url/external_url_config.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.IExternalUrlPolicy.allow", + "type": "boolean", + "tags": [], + "label": "allow", + "description": [ + "\nIndicates if this policy allows or denies access to the described destination." + ], + "path": "src/core/server/http/external_url/external_url_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.IExternalUrlPolicy.host", + "type": "string", + "tags": [], + "label": "host", + "description": [ + "\nOptional host describing the external destination.\nMay be combined with `protocol`.\n" + ], + "signature": [ + "string | undefined" + ], + "path": "src/core/server/http/external_url/external_url_config.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.IExternalUrlPolicy.protocol", + "type": "string", + "tags": [], + "label": "protocol", + "description": [ + "\nOptional protocol describing the external destination.\nMay be combined with `host`.\n" + ], + "signature": [ + "string | undefined" + ], + "path": "src/core/server/http/external_url/external_url_config.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.IKibanaResponse", @@ -6781,16 +7022,16 @@ "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" }, " = ", { "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" }, ", Method extends ", { @@ -10430,11 +10671,11 @@ "section": "def-server.OnPreAuthToolkit", "text": "OnPreAuthToolkit" }, - ") => ", + ") => Next | ", "KibanaResponse", - " | Next | Promise<", + " | Promise | Next>" + ">" ], "path": "src/core/server/http/lifecycle/on_pre_auth.ts", "deprecated": false, @@ -10886,11 +11127,11 @@ "section": "def-server.OnPreRoutingToolkit", "text": "OnPreRoutingToolkit" }, - ") => OnPreRoutingResult | ", + ") => ", "KibanaResponse", - " | Promise | OnPreRoutingResult | Promise<", "KibanaResponse", - ">" + " | OnPreRoutingResult>" ], "path": "src/core/server/http/lifecycle/on_pre_routing.ts", "deprecated": false, @@ -11673,16 +11914,16 @@ "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" }, " = ", { "pluginId": "core", "scope": "server", "docId": "kibCorePluginApi", - "section": "def-server.RequestHandlerContext", - "text": "RequestHandlerContext" + "section": "def-server.RequestHandlerContextBase", + "text": "RequestHandlerContextBase" }, ", Method extends ", { diff --git a/api_docs/core_http.mdx b/api_docs/core_http.mdx index fa0e5038f9e8d..495d3d0a2357c 100644 --- a/api_docs/core_http.mdx +++ b/api_docs/core_http.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-http title: "core.http" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.http plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.http'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2525 | 15 | 914 | 28 | +| 2546 | 15 | 926 | 28 | ## Client diff --git a/api_docs/core_saved_objects.devdocs.json b/api_docs/core_saved_objects.devdocs.json index 540ce398b4990..7077e7a0cde87 100644 --- a/api_docs/core_saved_objects.devdocs.json +++ b/api_docs/core_saved_objects.devdocs.json @@ -12204,6 +12204,557 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext", + "type": "Interface", + "tags": [], + "label": "SavedObjectsRequestHandlerContext", + "description": [ + "\nCore's `savedObjects` request handler context." + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.client", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + "{ create: (type: string, attributes: T, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreateOptions", + "text": "SavedObjectsCreateOptions" + }, + " | undefined) => Promise<", + "SavedObject", + ">; bulkCreate: (objects: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkCreateObject", + "text": "SavedObjectsBulkCreateObject" + }, + "[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreateOptions", + "text": "SavedObjectsCreateOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkResponse", + "text": "SavedObjectsBulkResponse" + }, + ">; checkConflicts: (objects?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCheckConflictsObject", + "text": "SavedObjectsCheckConflictsObject" + }, + "[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + }, + ") => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCheckConflictsResponse", + "text": "SavedObjectsCheckConflictsResponse" + }, + ">; delete: (type: string, id: string, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsDeleteOptions", + "text": "SavedObjectsDeleteOptions" + }, + ") => Promise<{}>; find: (options: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindOptions", + "text": "SavedObjectsFindOptions" + }, + ") => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsFindResponse", + "text": "SavedObjectsFindResponse" + }, + ">; bulkGet: (objects?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkGetObject", + "text": "SavedObjectsBulkGetObject" + }, + "[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + }, + ") => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkResponse", + "text": "SavedObjectsBulkResponse" + }, + ">; bulkResolve: (objects: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkResolveObject", + "text": "SavedObjectsBulkResolveObject" + }, + "[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkResolveResponse", + "text": "SavedObjectsBulkResolveResponse" + }, + ">; get: (type: string, id: string, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + }, + ") => Promise<", + "SavedObject", + ">; resolve: (type: string, id: string, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + }, + ") => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsResolveResponse", + "text": "SavedObjectsResolveResponse" + }, + ">; update: (type: string, id: string, attributes: Partial, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsUpdateOptions", + "text": "SavedObjectsUpdateOptions" + }, + ") => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsUpdateResponse", + "text": "SavedObjectsUpdateResponse" + }, + ">; collectMultiNamespaceReferences: (objects: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCollectMultiNamespaceReferencesObject", + "text": "SavedObjectsCollectMultiNamespaceReferencesObject" + }, + "[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCollectMultiNamespaceReferencesOptions", + "text": "SavedObjectsCollectMultiNamespaceReferencesOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCollectMultiNamespaceReferencesResponse", + "text": "SavedObjectsCollectMultiNamespaceReferencesResponse" + }, + ">; updateObjectsSpaces: (objects: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsUpdateObjectsSpacesObject", + "text": "SavedObjectsUpdateObjectsSpacesObject" + }, + "[], spacesToAdd: string[], spacesToRemove: string[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsUpdateObjectsSpacesOptions", + "text": "SavedObjectsUpdateObjectsSpacesOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsUpdateObjectsSpacesResponse", + "text": "SavedObjectsUpdateObjectsSpacesResponse" + }, + ">; bulkUpdate: (objects: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkUpdateObject", + "text": "SavedObjectsBulkUpdateObject" + }, + "[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkUpdateOptions", + "text": "SavedObjectsBulkUpdateOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBulkUpdateResponse", + "text": "SavedObjectsBulkUpdateResponse" + }, + ">; removeReferencesTo: (type: string, id: string, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsRemoveReferencesToOptions", + "text": "SavedObjectsRemoveReferencesToOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsRemoveReferencesToResponse", + "text": "SavedObjectsRemoveReferencesToResponse" + }, + ">; openPointInTimeForType: (type: string | string[], options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsOpenPointInTimeOptions", + "text": "SavedObjectsOpenPointInTimeOptions" + }, + ") => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsOpenPointInTimeResponse", + "text": "SavedObjectsOpenPointInTimeResponse" + }, + ">; closePointInTime: (id: string, options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsBaseOptions", + "text": "SavedObjectsBaseOptions" + }, + " | undefined) => Promise<", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClosePointInTimeResponse", + "text": "SavedObjectsClosePointInTimeResponse" + }, + ">; createPointInTimeFinder: (findOptions: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreatePointInTimeFinderOptions", + "text": "SavedObjectsCreatePointInTimeFinderOptions" + }, + ", dependencies?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsCreatePointInTimeFinderDependencies", + "text": "SavedObjectsCreatePointInTimeFinderDependencies" + }, + " | undefined) => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.ISavedObjectsPointInTimeFinder", + "text": "ISavedObjectsPointInTimeFinder" + }, + "; errors: typeof ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsErrorHelpers", + "text": "SavedObjectsErrorHelpers" + }, + "; }" + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.typeRegistry", + "type": "Object", + "tags": [], + "label": "typeRegistry", + "description": [], + "signature": [ + "{ getType: (type: string) => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsType", + "text": "SavedObjectsType" + }, + " | undefined; getVisibleTypes: () => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]; getAllTypes: () => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]; getImportableAndExportableTypes: () => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsType", + "text": "SavedObjectsType" + }, + "[]; isNamespaceAgnostic: (type: string) => boolean; isSingleNamespace: (type: string) => boolean; isMultiNamespace: (type: string) => boolean; isShareable: (type: string) => boolean; isHidden: (type: string) => boolean; getIndex: (type: string) => string | undefined; isImportableAndExportable: (type: string) => boolean; }" + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.getClient", + "type": "Function", + "tags": [], + "label": "getClient", + "description": [], + "signature": [ + "(options?: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientProviderOptions", + "text": "SavedObjectsClientProviderOptions" + }, + " | undefined) => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.getClient.$1", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientProviderOptions", + "text": "SavedObjectsClientProviderOptions" + }, + " | undefined" + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.getExporter", + "type": "Function", + "tags": [], + "label": "getExporter", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ") => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.ISavedObjectsExporter", + "text": "ISavedObjectsExporter" + } + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.getExporter.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.getImporter", + "type": "Function", + "tags": [], + "label": "getImporter", + "description": [], + "signature": [ + "(client: ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ") => ", + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.ISavedObjectsImporter", + "text": "ISavedObjectsImporter" + } + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.SavedObjectsRequestHandlerContext.getImporter.$1", + "type": "Object", + "tags": [], + "label": "client", + "description": [], + "signature": [ + { + "pluginId": "core", + "scope": "server", + "docId": "kibCoreSavedObjectsPluginApi", + "section": "def-server.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "src/core/server/saved_objects/saved_objects_route_handler_context.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.SavedObjectsResolveImportErrorsOptions", diff --git a/api_docs/core_saved_objects.mdx b/api_docs/core_saved_objects.mdx index 48b69e88f01bb..07a071c7471ce 100644 --- a/api_docs/core_saved_objects.mdx +++ b/api_docs/core_saved_objects.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/core-savedObjects title: "core.savedObjects" image: https://source.unsplash.com/400x175/?github summary: API docs for the core.savedObjects plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core.savedObjects'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2525 | 15 | 914 | 28 | +| 2546 | 15 | 926 | 28 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 70e8d78a3de87..3af44e5fc8fc7 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github summary: API docs for the customIntegrations plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index fbbb35e8c581d..7d1e79199770f 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github summary: API docs for the dashboard plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 0770925dc334f..bb414e95c96f2 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the dashboardEnhanced plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index b1b290efefc96..21320db919b4e 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -4328,7 +4328,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined" + " | ", + "MatchAllRangeFilter", + " | undefined" ], "path": "src/plugins/data/common/query/timefilter/get_time.ts", "deprecated": false, @@ -14551,7 +14553,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined" + " | ", + "MatchAllRangeFilter", + " | undefined" ], "path": "src/plugins/data/common/query/timefilter/get_time.ts", "deprecated": false, diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 665c772321923..57ce00b9279ec 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github summary: API docs for the data plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_query.devdocs.json b/api_docs/data_query.devdocs.json index e888da21c8a41..e8ed18e14eced 100644 --- a/api_docs/data_query.devdocs.json +++ b/api_docs/data_query.devdocs.json @@ -2881,7 +2881,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined; createRelativeFilter: (indexPattern: ", + " | ", + "MatchAllRangeFilter", + " | undefined; createRelativeFilter: (indexPattern: ", { "pluginId": "dataViews", "scope": "common", @@ -2895,7 +2897,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined; getBounds: () => ", + " | ", + "MatchAllRangeFilter", + " | undefined; getBounds: () => ", { "pluginId": "data", "scope": "common", @@ -3121,7 +3125,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined; createRelativeFilter: (indexPattern: ", + " | ", + "MatchAllRangeFilter", + " | undefined; createRelativeFilter: (indexPattern: ", { "pluginId": "dataViews", "scope": "common", @@ -3135,7 +3141,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined; getBounds: () => ", + " | ", + "MatchAllRangeFilter", + " | undefined; getBounds: () => ", { "pluginId": "data", "scope": "common", @@ -3350,7 +3358,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined" + " | ", + "MatchAllRangeFilter", + " | undefined" ], "path": "src/plugins/data/common/query/timefilter/get_time.ts", "deprecated": false, @@ -3454,7 +3464,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined" + " | ", + "MatchAllRangeFilter", + " | undefined" ], "path": "src/plugins/data/common/query/timefilter/get_time.ts", "deprecated": false, diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 35e12e5b6fb33..f1b94acc51e91 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github summary: API docs for the data.query plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index a3c2313510964..f74453bbfbb2a 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github summary: API docs for the data.search plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 2e2bd6c6fb2b3..936442157991d 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViewEditor plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 3ebc2d5a5a97a..a449db9f42684 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViewFieldEditor plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index 75c622ae5abdb..ec5aba2e5feb1 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViewManagement plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index a9007c3295dc1..ea451e2dfb586 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataViews plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 1b4a297a8aca0..e5b9b289744a8 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github summary: API docs for the dataVisualizer plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index d5e64b08332e4..b34e626c09f23 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API summary: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index 0bb265e66bd40..ff3b4d5e41d6a 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin summary: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 233abd8838f1e..0adb4e0768373 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -3,7 +3,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team summary: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 698f790a3a66e..edc8afc8e03c8 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github summary: API docs for the devTools plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 7eed6b5a74359..dce0511d0c9f9 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github summary: API docs for the discover plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index 11cd7bdab2f25..d3fa906b068ad 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the discoverEnhanced plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/elastic_apm_synthtrace.mdx b/api_docs/elastic_apm_synthtrace.mdx index 4cf2aea8c4c65..9b697178e1d7f 100644 --- a/api_docs/elastic_apm_synthtrace.mdx +++ b/api_docs/elastic_apm_synthtrace.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/elastic-apm-synthtrace title: "@elastic/apm-synthtrace" image: https://source.unsplash.com/400x175/?github summary: API docs for the @elastic/apm-synthtrace plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@elastic/apm-synthtrace'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 88f836dfe2cfa..1d07d05ec7df6 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github summary: API docs for the embeddable plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index aa1690823ca17..65e14dc48de58 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the embeddableEnhanced plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 375f8d5fc10eb..1d7de027fec43 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github summary: API docs for the encryptedSavedObjects plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index 84242c541fba2..e72985ac65ed0 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github summary: API docs for the enterpriseSearch plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index 2b6f39a07750f..a8b4cc3e26bd5 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github summary: API docs for the esUiShared plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 57cd28af43b70..6601b780dd339 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github summary: API docs for the eventAnnotation plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 1dc589faadc84..6d1a1450e5b24 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github summary: API docs for the eventLog plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index eaf691a042530..43abf33395fcd 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionError plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index c2f057c4cde16..f1760248bfae7 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionGauge plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index 794ec314f90f0..a01d55465de4f 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionHeatmap plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index b72c6116e61a7..8fa747b925a89 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionImage plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index 76d6c48f79d84..9e29a38747de3 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionMetric plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index 8ffd404412284..2e0c5b1e1a2e9 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionMetricVis plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index c98b791b69409..e7244a0452042 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionPartitionVis plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 7043bffddd6c5..9e622ed335dea 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionRepeatImage plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index 330c820f403e2..1f87a2aa4f039 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionRevealImage plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index 2bd5b14a359ee..f3f7049bc5891 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionShape plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index 6b6448d1b0543..f0bdf54f16009 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionTagcloud plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 000a4755237e5..e8d90fc2554b4 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressionXY plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index e60a916d90d02..3946605ca462e 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github summary: API docs for the expressions plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 5e9fc577a6949..b4f4ae0c842db 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github summary: API docs for the features plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index b0ccf0e0b8184..617326915fb28 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github summary: API docs for the fieldFormats plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 10a8637b1530e..87b93c64c6098 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github summary: API docs for the fileUpload plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 6911a3ccadf82..0dd6660ad8283 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -17633,6 +17633,32 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "fleet", + "id": "def-common.PostBulkUpdateAgentTagsRequest", + "type": "Interface", + "tags": [], + "label": "PostBulkUpdateAgentTagsRequest", + "description": [], + "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-common.PostBulkUpdateAgentTagsRequest.body", + "type": "Object", + "tags": [], + "label": "body", + "description": [], + "signature": [ + "{ agents: string | string[]; tagsToAdd?: string[] | undefined; tagsToRemove?: string[] | undefined; }" + ], + "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "fleet", "id": "def-common.PostDownloadSourceRequest", @@ -23542,6 +23568,21 @@ ], "returnComment": [] }, + { + "parentPluginId": "fleet", + "id": "def-common.agentRouteService.getBulkUpdateTagsPath", + "type": "Function", + "tags": [], + "label": "getBulkUpdateTagsPath", + "description": [], + "signature": [ + "() => string" + ], + "path": "x-pack/plugins/fleet/common/services/routes.ts", + "deprecated": false, + "children": [], + "returnComment": [] + }, { "parentPluginId": "fleet", "id": "def-common.agentRouteService.getUnenrollPath", diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index ba7cbd4908772..6c4bac4927ad7 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github summary: API docs for the fleet plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1504 | 8 | 1373 | 10 | +| 1507 | 8 | 1376 | 10 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index bb56934aab8d1..b0f44b242c70e 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github summary: API docs for the globalSearch plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/home.mdx b/api_docs/home.mdx index f78ddd08e73ba..aa7294c249f21 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github summary: API docs for the home plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 31ff9d3cff3be..d3c745ffb80f4 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the indexLifecycleManagement plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 17fd7a713acf2..ae066f8581e2c 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the indexManagement plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 6bfe34f47104e..89500dd59e06e 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github summary: API docs for the infra plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index ac939ab560e1b..654ef3b19441a 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github summary: API docs for the inspector plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index aa5e4ec412856..6050680a81226 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github summary: API docs for the interactiveSetup plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index cb24c054644a7..7d92c3d3c2e79 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ace plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_aiops_components.devdocs.json b/api_docs/kbn_aiops_components.devdocs.json new file mode 100644 index 0000000000000..3f97c3f94849c --- /dev/null +++ b/api_docs/kbn_aiops_components.devdocs.json @@ -0,0 +1,121 @@ +{ + "id": "@kbn/aiops-components", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.DualBrush", + "type": "Function", + "tags": [], + "label": "DualBrush", + "description": [], + "signature": [ + "({\n windowParameters,\n min,\n max,\n onChange,\n marginLeft,\n width,\n}: DualBrushProps) => JSX.Element" + ], + "path": "x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.DualBrush.$1", + "type": "Object", + "tags": [], + "label": "{\n windowParameters,\n min,\n max,\n onChange,\n marginLeft,\n width,\n}", + "description": [], + "signature": [ + "DualBrushProps" + ], + "path": "x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.DualBrushAnnotation", + "type": "Function", + "tags": [], + "label": "DualBrushAnnotation", + "description": [], + "signature": [ + "({ id, min, max }: React.PropsWithChildren) => JSX.Element" + ], + "path": "x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush_annotation.tsx", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.DualBrushAnnotation.$1", + "type": "CompoundType", + "tags": [], + "label": "{ id, min, max }", + "description": [], + "signature": [ + "React.PropsWithChildren" + ], + "path": "x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush_annotation.tsx", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.ProgressControls", + "type": "Function", + "tags": [], + "label": "ProgressControls", + "description": [], + "signature": [ + "({\n progress,\n progressMessage,\n onRefresh,\n onCancel,\n isRunning,\n}: ProgressControlProps) => JSX.Element" + ], + "path": "x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/aiops-components", + "id": "def-common.ProgressControls.$1", + "type": "Object", + "tags": [], + "label": "{\n progress,\n progressMessage,\n onRefresh,\n onCancel,\n isRunning,\n}", + "description": [], + "signature": [ + "ProgressControlProps" + ], + "path": "x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx new file mode 100644 index 0000000000000..a2bcf1c03fe76 --- /dev/null +++ b/api_docs/kbn_aiops_components.mdx @@ -0,0 +1,27 @@ +--- +id: kibKbnAiopsComponentsPluginApi +slug: /kibana-dev-docs/api/kbn-aiops-components +title: "@kbn/aiops-components" +image: https://source.unsplash.com/400x175/?github +summary: API docs for the @kbn/aiops-components plugin +date: 2022-07-07 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] +warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. +--- +import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; + +React components for AIOps related efforts. + +Contact Machine Learning UI for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 6 | 0 | 6 | 0 | + +## Common + +### Functions + + diff --git a/api_docs/kbn_aiops_utils.devdocs.json b/api_docs/kbn_aiops_utils.devdocs.json index 697141d149e6a..3efcc1adf9861 100644 --- a/api_docs/kbn_aiops_utils.devdocs.json +++ b/api_docs/kbn_aiops_utils.devdocs.json @@ -38,7 +38,7 @@ "text": "WindowParameters" } ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false, "children": [ { @@ -53,7 +53,7 @@ "signature": [ "number" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false, "isRequired": true }, @@ -69,7 +69,7 @@ "signature": [ "number" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false, "isRequired": true }, @@ -85,7 +85,7 @@ "signature": [ "number" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false, "isRequired": true } @@ -95,37 +95,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "@kbn/aiops-utils", - "id": "def-common.ProgressControls", - "type": "Function", - "tags": [], - "label": "ProgressControls", - "description": [], - "signature": [ - "({\n progress,\n progressMessage,\n onRefresh,\n onCancel,\n isRunning,\n}: ProgressControlProps) => JSX.Element" - ], - "path": "x-pack/packages/ml/aiops_utils/src/components/progress_controls.tsx", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/aiops-utils", - "id": "def-common.ProgressControls.$1", - "type": "Object", - "tags": [], - "label": "{\n progress,\n progressMessage,\n onRefresh,\n onCancel,\n isRunning,\n}", - "description": [], - "signature": [ - "ProgressControlProps" - ], - "path": "x-pack/packages/ml/aiops_utils/src/components/progress_controls.tsx", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/aiops-utils", "id": "def-common.streamFactory", @@ -140,7 +109,7 @@ "Logger", ") => StreamFactoryReturnType" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts", + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, "children": [ { @@ -155,7 +124,7 @@ "signature": [ "Headers" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts", + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, "isRequired": true }, @@ -169,7 +138,7 @@ "signature": [ "Logger" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts", + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, "isRequired": true } @@ -193,7 +162,7 @@ "Logger", ") => StreamFactoryReturnType" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts", + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, "children": [ { @@ -208,7 +177,7 @@ "signature": [ "Headers" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts", + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, "isRequired": true }, @@ -222,7 +191,7 @@ "signature": [ "Logger" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts", + "path": "x-pack/packages/ml/aiops_utils/src/stream_factory.ts", "deprecated": false, "isRequired": true } @@ -242,7 +211,7 @@ "signature": [ "(endpoint: `${BasePath}${I[\"endpoint\"]}`, body: I[\"body\"]) => UseFetchStreamReturnType>" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -255,7 +224,7 @@ "signature": [ "`${BasePath}${I[\"endpoint\"]}`" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "isRequired": true }, @@ -269,7 +238,7 @@ "signature": [ "I[\"body\"]" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "isRequired": true } @@ -287,7 +256,7 @@ "signature": [ "(endpoint: `${BasePath}${I[\"endpoint\"]}`, body: I[\"body\"], options: { reducer: I[\"reducer\"]; initialState: React.ReducerState; }) => UseFetchStreamReturnType, React.ReducerAction>" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -300,7 +269,7 @@ "signature": [ "`${BasePath}${I[\"endpoint\"]}`" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "isRequired": true }, @@ -314,7 +283,7 @@ "signature": [ "I[\"body\"]" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "isRequired": true }, @@ -325,7 +294,7 @@ "tags": [], "label": "options", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -338,7 +307,7 @@ "signature": [ "I[\"reducer\"]" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "returnComment": [], "children": [ @@ -380,7 +349,7 @@ "signature": [ "I[\"reducer\"] extends React.Reducer ? S : never" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false } ] @@ -401,7 +370,7 @@ "signature": [ "(endpoint: `${BasePath}${I[\"endpoint\"]}`, body: I[\"body\"], options: { reducer: I[\"reducer\"]; initialState: React.ReducerState; } | undefined) => UseFetchStreamReturnType, React.ReducerAction>" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -416,7 +385,7 @@ "signature": [ "`${BasePath}${I[\"endpoint\"]}`" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "isRequired": true }, @@ -432,7 +401,7 @@ "signature": [ "I[\"body\"]" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "isRequired": true }, @@ -443,7 +412,7 @@ "tags": [], "label": "options", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -456,7 +425,7 @@ "signature": [ "I[\"reducer\"]" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "returnComment": [], "children": [ @@ -498,7 +467,7 @@ "signature": [ "I[\"reducer\"] extends React.Reducer ? S : never" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false } ] @@ -520,7 +489,7 @@ "description": [ "\nCustom hook type definition of the base params for an NDJSON stream with custom reducer." ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -530,7 +499,7 @@ "tags": [], "label": "endpoint", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false }, { @@ -543,7 +512,7 @@ "signature": [ "object" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false }, { @@ -556,7 +525,7 @@ "signature": [ "(prevState: any, action: any) => any" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "returnComment": [], "children": [ @@ -600,7 +569,7 @@ "description": [ "\nCustom hook type definition of the base params for a string base stream without a custom reducer." ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "children": [ { @@ -610,7 +579,7 @@ "tags": [], "label": "endpoint", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false }, { @@ -623,7 +592,7 @@ "signature": [ "object" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false }, { @@ -636,7 +605,7 @@ "signature": [ "(prevState: string, action: StringReducerPayload) => string" ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts", + "path": "x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts", "deprecated": false, "returnComment": [], "children": [ @@ -680,7 +649,7 @@ "description": [ "\nTime range definition for baseline and deviation to be used by spike log analysis." ], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false, "children": [ { @@ -690,7 +659,7 @@ "tags": [], "label": "baselineMin", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false }, { @@ -700,7 +669,7 @@ "tags": [], "label": "baselineMax", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false }, { @@ -710,7 +679,7 @@ "tags": [], "label": "deviationMin", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false }, { @@ -720,7 +689,7 @@ "tags": [], "label": "deviationMax", "description": [], - "path": "x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts", + "path": "x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts", "deprecated": false } ], diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index e5b0089376945..7569216af2b73 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/aiops-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact Machine Learning UI for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 48 | 0 | 26 | 0 | +| 46 | 0 | 24 | 0 | ## Common diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index ebeee5bd01264..455d2ea64c902 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/alerts plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index f7b6ceaedf7c9..f489255cbd4ad 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 6687f03554de5..8538bc1d45c74 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-client plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json b/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json index 9ef9bce246736..e3b9cb0237cfb 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.devdocs.json @@ -258,7 +258,7 @@ "tags": [], "label": "ElasticV3ShipperOptions", "description": [ - "\r\nOptions for the Elastic V3 shipper" + "\nOptions for the Elastic V3 shipper" ], "path": "node_modules/@types/kbn__analytics-shippers-elastic-v3-common/index.d.ts", "deprecated": false, @@ -270,7 +270,7 @@ "tags": [], "label": "channelName", "description": [ - "\r\nThe name of the channel to stream all the events to." + "\nThe name of the channel to stream all the events to." ], "path": "node_modules/@types/kbn__analytics-shippers-elastic-v3-common/index.d.ts", "deprecated": false @@ -282,7 +282,7 @@ "tags": [], "label": "version", "description": [ - "\r\nThe product's version." + "\nThe product's version." ], "path": "node_modules/@types/kbn__analytics-shippers-elastic-v3-common/index.d.ts", "deprecated": false @@ -294,7 +294,7 @@ "tags": [], "label": "sendTo", "description": [ - "\r\nProvide it to override the Analytics client's default configuration." + "\nProvide it to override the Analytics client's default configuration." ], "signature": [ "\"staging\" | \"production\" | undefined" @@ -309,7 +309,7 @@ "tags": [], "label": "debug", "description": [ - "\r\nShould show debug information about the requests it makes to the V3 API." + "\nShould show debug information about the requests it makes to the V3 API." ], "signature": [ "boolean | undefined" diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 0bfb58c382899..439bcfb65c219 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index da4ef5d94d81e..5de1496ac4bd8 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json b/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json index fc65d103d3a59..4f0c71a709e9d 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.devdocs.json @@ -250,7 +250,7 @@ "tags": [], "label": "ElasticV3ShipperOptions", "description": [ - "\r\nOptions for the Elastic V3 shipper" + "\nOptions for the Elastic V3 shipper" ], "path": "node_modules/@types/kbn__analytics-shippers-elastic-v3-common/index.d.ts", "deprecated": false, @@ -262,7 +262,7 @@ "tags": [], "label": "channelName", "description": [ - "\r\nThe name of the channel to stream all the events to." + "\nThe name of the channel to stream all the events to." ], "path": "node_modules/@types/kbn__analytics-shippers-elastic-v3-common/index.d.ts", "deprecated": false @@ -274,7 +274,7 @@ "tags": [], "label": "version", "description": [ - "\r\nThe product's version." + "\nThe product's version." ], "path": "node_modules/@types/kbn__analytics-shippers-elastic-v3-common/index.d.ts", "deprecated": false @@ -286,7 +286,7 @@ "tags": [], "label": "sendTo", "description": [ - "\r\nProvide it to override the Analytics client's default configuration." + "\nProvide it to override the Analytics client's default configuration." ], "signature": [ "\"staging\" | \"production\" | undefined" @@ -301,7 +301,7 @@ "tags": [], "label": "debug", "description": [ - "\r\nShould show debug information about the requests it makes to the V3 API." + "\nShould show debug information about the requests it makes to the V3 API." ], "signature": [ "boolean | undefined" diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index 5799ad575960c..c28461639e964 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 664bc45c94908..9e34583c069d5 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index 33272b97afa21..cf32d29ef01a0 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/apm-config-loader plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index f48270a33f010..95124f9b663fe 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/apm-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index eb08c111ee69d..9942e56e56780 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/axe-config plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_bazel_packages.mdx b/api_docs/kbn_bazel_packages.mdx index cdec0abf52511..ca32c5729d338 100644 --- a/api_docs/kbn_bazel_packages.mdx +++ b/api_docs/kbn_bazel_packages.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-bazel-packages title: "@kbn/bazel-packages" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/bazel-packages plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bazel-packages'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_bazel_runner.mdx b/api_docs/kbn_bazel_runner.mdx index 0a1c22790a6ad..37a7b85f49ccc 100644 --- a/api_docs/kbn_bazel_runner.mdx +++ b/api_docs/kbn_bazel_runner.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-bazel-runner title: "@kbn/bazel-runner" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/bazel-runner plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/bazel-runner'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index e89105feaa576..aa3b9b6a8ed95 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ci-stats-core plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index d31507fb2e8a9..4b3d2f7772c31 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 508cc8f5b5089..e27287afb89f3 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index ee45e492874f6..435b6aef6edba 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/cli-dev-mode plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 911828986be89..a6a2e2e8bb855 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/coloring plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index e1e118cef2d25..97c60231c1095 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/config plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 8dbcff899bcc9..867574b1a5b27 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/config-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index 33901667623f1..3b70fe3167c71 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/config-schema plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index e9eb480842904..fdd5968bfcc29 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-analytics-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 5ce99ef3d822e..0092d3e53fd16 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 8f0646b7067df..4c25cdd3c0481 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 718feb6d481a8..e3cfedebcfc81 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-analytics-server plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 81be2b6954c9d..f33c60fbf00bb 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index 572dbecd470e7..8fcde0b4562b0 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index 784233e669604..59b8d6971aaa6 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 9e789297e2f14..f00a90b8901e7 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-base-common plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index a16c10cf715ab..cf9e0e35d44fd 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-base-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index b12145fbbc5f1..d4ff161ca1cb6 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 33b7cfb3755c1..6cd145d5f780f 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-config-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 7233a10e0d9b0..3cba36bfd3e85 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index b21b02a30722c..7cc23b6101626 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 2076cf554bd20..fa1f03d12fa3d 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-doc-links-server plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 7a4fa2289d87a..c5b7deabc005e 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 63f5e7981b598..a2cdf3ff91b3e 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index bd7b5dca84f06..1a64a8cf1d2f5 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 59336f0413474..b4083df998e31 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index 3b54937d8e600..1ddfa9149fec8 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 57ffba13bef8d..5c3bbfb81303d 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index 1feb3263b4fb1..09a7155586219 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-common plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 51b5f3b344ced..9b5dc5c5cfe79 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-server plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index dca45d9a7be1d..3b10129f5b992 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 393d8e474cd15..3c7aba6f47d5a 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index d9c3d6d69680b..981dbb44be5e5 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index 279ce15f26988..538ed4d9015d1 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index d7b1b1a50c438..0a112d36ec59e 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-i18n-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 9f1affe365f3f..9142e07f50b8b 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 8ae7f64551bfe..6a3f9bb46fce3 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 9e79ca40deb2c..2bc455883ffd4 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index c65b6dd74aea4..3ccf292bf50f4 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-logging-server plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index f8d76eed39f29..578bd148e5450 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index d23c1744baf94..82867cca62f94 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index d121bf530bcea..98ce97dfcdc7d 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-node-server plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 6f61e236c48b4..f6e856cba4b60 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-node-server-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index 6a8ca1c3641ac..c454839e66127 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 843a4a5b790b0..343f246c13c44 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-theme-browser plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 275bf092dae6d..1bc8f154d6b73 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 05755e17ea9e5..a16929776da91 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index c0ac3b48435c0..9037a33f963b5 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/crypto plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index d64a233d17d34..67c8dcf553924 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/datemath plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 389efa73e7c16..c18b84797d231 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-cli-errors plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 9c6705a22836f..ba21e0da73f29 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-cli-runner plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index eaa943f59a848..748b6e36ddc5c 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-proc-runner plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index da519febef9a7..eade470719f03 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/dev-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 96a82bbafe558..bd246ee64b743 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/doc-links plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index 9a7ac412650ef..37d6d5d0d2740 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/docs-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index ed96e0abd72b6..333902acb6c5c 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/es-archiver plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 4c908787f3277..2171b86ce5786 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/es-errors plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index c07adc2d51210..4b05fb220099c 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/es-query plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 6506b3f4084dc..9ce570027be0e 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 363633710fe2a..d8fbe3904e65b 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/field-types plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 22b35802f36da..289a566614ac9 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/find-used-node-modules plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 38c4acf6b2786..3be5faed0d49e 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/generate plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index ce37ecba885a6..6e1e2d0efc644 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/handlebars plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_home_sample_data_cards.mdx b/api_docs/kbn_home_sample_data_cards.mdx index b7627c54ac784..abfac66cc4eec 100644 --- a/api_docs/kbn_home_sample_data_cards.mdx +++ b/api_docs/kbn_home_sample_data_cards.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-cards title: "@kbn/home-sample-data-cards" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/home-sample-data-cards plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-cards'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index b6879eb47a17a..90d9352be1143 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/i18n plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 6b42e8d7e28fa..b745b1fe0b8d9 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/import-resolver plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index 87ec6df6e300b..2a7f2c86cc292 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/interpreter plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index f86f58c63a59e..9dd48ef211ee2 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/io-ts-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 9bbe9109ba0a0..21508e4b2eff7 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/jest-serializers plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_kibana_json_schema.mdx b/api_docs/kbn_kibana_json_schema.mdx index 4550d0e5915ed..aa34b2a2674cd 100644 --- a/api_docs/kbn_kibana_json_schema.mdx +++ b/api_docs/kbn_kibana_json_schema.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-json-schema title: "@kbn/kibana-json-schema" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/kibana-json-schema plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-json-schema'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 67c2d6bea3653..eead18cde8c0c 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/logging plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 3ab79e27c0c60..2b10e1314f79f 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/logging-mocks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 74ad735224c02..247d7f9d2055c 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/mapbox-gl plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 3339d2245c640..3614ced9307d9 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ml-agg-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 6567afd0f21d2..9476a87594fec 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 833ccbeadc4ae..038328b61acfb 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ml-string-hash plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 71cab510fe3f3..6a92ed2409187 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/monaco plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 56fa75eba229b..1ba0934dc0a4a 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/optimizer plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index d86a3980763fd..3997039d29838 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index bff3ae05911b8..9ec9f45b492e2 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_plugin_discovery.mdx b/api_docs/kbn_plugin_discovery.mdx index c0a762ecf9b8f..0c064f4add9ec 100644 --- a/api_docs/kbn_plugin_discovery.mdx +++ b/api_docs/kbn_plugin_discovery.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-discovery title: "@kbn/plugin-discovery" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/plugin-discovery plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-discovery'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 285a98781a8f3..da6adc658fbd0 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/plugin-generator plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index a09ee10759cfd..d3b1590dccb92 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/plugin-helpers plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_pm.mdx b/api_docs/kbn_pm.mdx index c982ed1d9b9ff..38bd994b31293 100644 --- a/api_docs/kbn_pm.mdx +++ b/api_docs/kbn_pm.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-pm title: "@kbn/pm" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/pm plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/pm'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index ab57294d3b379..2554c1fdf8485 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/react-field plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index 4bd73993e5fbf..db6a091f8c947 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/rule-data-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_scalability_simulation_generator.mdx b/api_docs/kbn_scalability_simulation_generator.mdx index 42b50c9773685..acfeabbb2e46e 100644 --- a/api_docs/kbn_scalability_simulation_generator.mdx +++ b/api_docs/kbn_scalability_simulation_generator.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-scalability-simulation-generator title: "@kbn/scalability-simulation-generator" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/scalability-simulation-generator plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/scalability-simulation-generator'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index fc90e5bcee92c..4023bb0c17e8d 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 429e83fb9c392..67eaae53085f5 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 2eda3fabac37e..61cd4a033be59 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index 1764e7cad9bd8..eb5d982e83fa4 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 204ce7082c48a..061da7997b34e 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index eca2bcb1b9d4a..cefed215fdd95 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 4a279fb95329a..9d828d1966227 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 1c54bfbb7f7d8..7acec52d76970 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 0f20c516c4a8d..bed6640ebea90 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index c9fad43c88be7..f4aa706e162b6 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 0df5bb208a314..3e19d4a9e9a05 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index b5966bcf154b8..4b8cbde1060ab 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-rules plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 56884e924d78d..3499df8dfef52 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index b1bbc46eb4864..7909a5cb8e799 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/securitysolution-utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index c97136ba98558..6880d28ee95f5 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/server-http-tools plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 02e9123052c67..79176223ab666 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/server-route-repository plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 9cf65d1038f50..fdda9ec0e0bd8 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 4922f34a73fb5..33aa4f1439168 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_components.mdx b/api_docs/kbn_shared_ux_components.mdx index af8e5bca17492..537f398f66d98 100644 --- a/api_docs/kbn_shared_ux_components.mdx +++ b/api_docs/kbn_shared_ux_components.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-components title: "@kbn/shared-ux-components" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-components plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-components'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 61d1eb574d2c5..f7863be4f3c7b 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 3cbfbe9930e50..f63e8788ed6c2 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 2af348609e793..61044da8eb3b8 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_services.mdx b/api_docs/kbn_shared_ux_services.mdx index da467990cec1f..7808d066b300d 100644 --- a/api_docs/kbn_shared_ux_services.mdx +++ b/api_docs/kbn_shared_ux_services.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-services title: "@kbn/shared-ux-services" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-services plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-services'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_storybook.devdocs.json b/api_docs/kbn_shared_ux_storybook.devdocs.json index 10b8b342f4cfb..82e09aac247b9 100644 --- a/api_docs/kbn_shared_ux_storybook.devdocs.json +++ b/api_docs/kbn_shared_ux_storybook.devdocs.json @@ -114,7 +114,9 @@ "\nA factory function for creating a Storybook implementation of `SharedUxEditorsService`." ], "signature": [ - "() => { openDataViewEditor: (options: DataViewEditorOptions) => () => void; }" + "() => { openDataViewEditor: (options: ", + "DataViewEditorOptions", + ") => () => void; }" ], "path": "packages/kbn-shared-ux-storybook/src/services/editors.ts", "deprecated": false, diff --git a/api_docs/kbn_shared_ux_storybook.mdx b/api_docs/kbn_shared_ux_storybook.mdx index c0321d296e3b2..7d25337317835 100644 --- a/api_docs/kbn_shared_ux_storybook.mdx +++ b/api_docs/kbn_shared_ux_storybook.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook title: "@kbn/shared-ux-storybook" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-storybook plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 5bc0e03238416..56eb2258e92f8 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/shared-ux-utility plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 77ba686ced3d9..959220f5c5324 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/sort-package-json plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index e435a855b837a..36cd94e040782 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/std plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index 4ad2206eea1e1..84ee7a37d5dfe 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index e7fcb59e4724c..ad622be1b6dae 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/storybook plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 98c8869ae67b1..d74508d23f399 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/telemetry-tools plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 8aba0f58df7d2..85de29a47cb15 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/test plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index f58aec72ec08a..ea16d49c713af 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/test-jest-helpers plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 436e53f0db236..62c001541eb5f 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/tooling-log plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_type_summarizer.devdocs.json b/api_docs/kbn_type_summarizer.devdocs.json index c1c42987dad2c..73abf92ebd70e 100644 --- a/api_docs/kbn_type_summarizer.devdocs.json +++ b/api_docs/kbn_type_summarizer.devdocs.json @@ -22,13 +22,7 @@ ], "signature": [ "(log: ", - { - "pluginId": "@kbn/type-summarizer", - "scope": "server", - "docId": "kibKbnTypeSummarizerPluginApi", - "section": "def-server.Logger", - "text": "Logger" - }, + "Logger", ", options: ", { "pluginId": "@kbn/type-summarizer", @@ -37,7 +31,9 @@ "section": "def-server.SummarizePacakgeOptions", "text": "SummarizePacakgeOptions" }, - ") => Promise" + ") => Promise<", + "SourceNode", + ">" ], "path": "packages/kbn-type-summarizer/src/summarize_package.ts", "deprecated": false, @@ -50,13 +46,7 @@ "label": "log", "description": [], "signature": [ - { - "pluginId": "@kbn/type-summarizer", - "scope": "server", - "docId": "kibKbnTypeSummarizerPluginApi", - "section": "def-server.Logger", - "text": "Logger" - } + "Logger" ], "path": "packages/kbn-type-summarizer/src/summarize_package.ts", "deprecated": false, @@ -88,321 +78,6 @@ } ], "interfaces": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger", - "type": "Interface", - "tags": [], - "label": "Logger", - "description": [ - "\nLogger interface used by @kbn/type-summarizer" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.info", - "type": "Function", - "tags": [], - "label": "info", - "description": [ - "\nWrite a message to the log with the level \"info\"" - ], - "signature": [ - "(msg: string, ...args: any[]) => void" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.info.$1", - "type": "string", - "tags": [], - "label": "msg", - "description": [ - "any message" - ], - "signature": [ - "string" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.info.$2", - "type": "Array", - "tags": [], - "label": "args", - "description": [ - "any serializeable values you would like to be appended to the log message" - ], - "signature": [ - "any[]" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.warn", - "type": "Function", - "tags": [], - "label": "warn", - "description": [ - "\nWrite a message to the log with the level \"warn\"" - ], - "signature": [ - "(msg: string, ...args: any[]) => void" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.warn.$1", - "type": "string", - "tags": [], - "label": "msg", - "description": [ - "any message" - ], - "signature": [ - "string" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.warn.$2", - "type": "Array", - "tags": [], - "label": "args", - "description": [ - "any serializeable values you would like to be appended to the log message" - ], - "signature": [ - "any[]" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.error", - "type": "Function", - "tags": [], - "label": "error", - "description": [ - "\nWrite a message to the log with the level \"error\"" - ], - "signature": [ - "(msg: string, ...args: any[]) => void" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.error.$1", - "type": "string", - "tags": [], - "label": "msg", - "description": [ - "any message" - ], - "signature": [ - "string" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.error.$2", - "type": "Array", - "tags": [], - "label": "args", - "description": [ - "any serializeable values you would like to be appended to the log message" - ], - "signature": [ - "any[]" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.debug", - "type": "Function", - "tags": [], - "label": "debug", - "description": [ - "\nWrite a message to the log with the level \"debug\"" - ], - "signature": [ - "(msg: string, ...args: any[]) => void" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.debug.$1", - "type": "string", - "tags": [], - "label": "msg", - "description": [ - "any message" - ], - "signature": [ - "string" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.debug.$2", - "type": "Array", - "tags": [], - "label": "args", - "description": [ - "any serializeable values you would like to be appended to the log message" - ], - "signature": [ - "any[]" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.verbose", - "type": "Function", - "tags": [], - "label": "verbose", - "description": [ - "\nWrite a message to the log with the level \"verbose\"" - ], - "signature": [ - "(msg: string, ...args: any[]) => void" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.verbose.$1", - "type": "string", - "tags": [], - "label": "msg", - "description": [ - "any message" - ], - "signature": [ - "string" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.verbose.$2", - "type": "Array", - "tags": [], - "label": "args", - "description": [ - "any serializeable values you would like to be appended to the log message" - ], - "signature": [ - "any[]" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.success", - "type": "Function", - "tags": [], - "label": "success", - "description": [ - "\nWrite a message to the log, only excluded in silent mode" - ], - "signature": [ - "(msg: string, ...args: any[]) => void" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.success.$1", - "type": "string", - "tags": [], - "label": "msg", - "description": [ - "any message" - ], - "signature": [ - "string" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.Logger.success.$2", - "type": "Array", - "tags": [], - "label": "args", - "description": [ - "any serializeable values you would like to be appended to the log message" - ], - "signature": [ - "any[]" - ], - "path": "packages/kbn-type-summarizer/src/lib/log/logger.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - } - ], - "initialIsOpen": false - }, { "parentPluginId": "@kbn/type-summarizer", "id": "def-server.SummarizePacakgeOptions", @@ -441,27 +116,12 @@ }, { "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.SummarizePacakgeOptions.inputPaths", - "type": "Array", - "tags": [], - "label": "inputPaths", - "description": [ - "\nArray of absolute paths to the .d.ts files which will be summarized. Each file in this\narray will cause an output .d.ts summary file to be created containing all the AST nodes\nwhich are exported or referenced by those exports." - ], - "signature": [ - "string[]" - ], - "path": "packages/kbn-type-summarizer/src/summarize_package.ts", - "deprecated": false - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.SummarizePacakgeOptions.outputDir", + "id": "def-server.SummarizePacakgeOptions.inputPath", "type": "string", "tags": [], - "label": "outputDir", + "label": "inputPath", "description": [ - "\nAbsolute path to the output directory where the summary .d.ts files should be written" + "\nArray of absolute paths to the .d.ts files which will be summarized. Each file in this\narray will cause an output .d.ts summary file to be created containing all the AST nodes\nwhich are exported or referenced by those exports." ], "path": "packages/kbn-type-summarizer/src/summarize_package.ts", "deprecated": false @@ -473,22 +133,7 @@ "tags": [], "label": "repoRelativePackageDir", "description": [ - "\nRepo-relative path to the package source, for example `packages/kbn-type-summarizer` for\nthis package. This is used to provide the correct `sourceRoot` path in the resulting source\nmap files." - ], - "path": "packages/kbn-type-summarizer/src/summarize_package.ts", - "deprecated": false - }, - { - "parentPluginId": "@kbn/type-summarizer", - "id": "def-server.SummarizePacakgeOptions.strictPrinting", - "type": "CompoundType", - "tags": [], - "label": "strictPrinting", - "description": [ - "\nShould the printer throw an error if it doesn't know how to print an AST node? Primarily\nused for testing" - ], - "signature": [ - "boolean | undefined" + "\nRepo-relative path to the package source, for example `packages/kbn-type-summarizer-core` for\nthis package. This is used to provide the correct `sourceRoot` path in the resulting source\nmap files." ], "path": "packages/kbn-type-summarizer/src/summarize_package.ts", "deprecated": false diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index b00e99fde2a56..066bc6d396b54 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/type-summarizer plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 29 | 0 | 2 | 0 | +| 8 | 0 | 2 | 0 | ## Server diff --git a/api_docs/kbn_type_summarizer_core.devdocs.json b/api_docs/kbn_type_summarizer_core.devdocs.json new file mode 100644 index 0000000000000..fefe4368c5d9c --- /dev/null +++ b/api_docs/kbn_type_summarizer_core.devdocs.json @@ -0,0 +1,1879 @@ +{ + "id": "@kbn/type-summarizer-core", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliError", + "type": "Class", + "tags": [], + "label": "CliError", + "description": [ + "\nAn error type with specicial behavior when it bubbles up all the way to the root of the CLI" + ], + "signature": [ + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.CliError", + "text": "CliError" + }, + " extends Error" + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliError.exitCode", + "type": "number", + "tags": [], + "label": "exitCode", + "description": [], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliError.showHelp", + "type": "boolean", + "tags": [], + "label": "showHelp", + "description": [], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliError.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliError.Unnamed.$1", + "type": "string", + "tags": [], + "label": "message", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliError.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "signature": [ + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.CliErrorOptions", + "text": "CliErrorOptions" + } + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog", + "type": "Class", + "tags": [], + "label": "CliLog", + "description": [ + "\nLogger which writes messages in a text format designed for CLIs" + ], + "signature": [ + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.CliLog", + "text": "CliLog" + }, + " implements ", + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.Logger", + "text": "Logger" + } + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.parseLogLevel", + "type": "Function", + "tags": [], + "label": "parseLogLevel", + "description": [], + "signature": [ + "(level: \"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\") => LogLevelMap" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.parseLogLevel.$1", + "type": "CompoundType", + "tags": [], + "label": "level", + "description": [], + "signature": [ + "\"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\"" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.pickLogLevelFromFlags", + "type": "Function", + "tags": [], + "label": "pickLogLevelFromFlags", + "description": [], + "signature": [ + "(flags: ", + "ParsedOptions", + ", defaultLogLevl?: \"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\") => \"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\"" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.pickLogLevelFromFlags.$1", + "type": "Object", + "tags": [], + "label": "flags", + "description": [], + "signature": [ + "ParsedOptions" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.pickLogLevelFromFlags.$2", + "type": "CompoundType", + "tags": [], + "label": "defaultLogLevl", + "description": [], + "signature": [ + "\"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\"" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.Unnamed.$1", + "type": "CompoundType", + "tags": [], + "label": "level", + "description": [], + "signature": [ + "\"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\"" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.Unnamed.$2", + "type": "Object", + "tags": [], + "label": "writeTo", + "description": [], + "signature": [ + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.LogWriter", + "text": "LogWriter" + } + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.Unnamed.$3", + "type": "boolean", + "tags": [], + "label": "writeTimes", + "description": [], + "signature": [ + "boolean" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.info", + "type": "Function", + "tags": [], + "label": "info", + "description": [], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.info.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.info.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.warn", + "type": "Function", + "tags": [], + "label": "warn", + "description": [], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.warn.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.warn.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.error", + "type": "Function", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.error.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.error.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.debug", + "type": "Function", + "tags": [], + "label": "debug", + "description": [], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.debug.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.debug.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verbose", + "type": "Function", + "tags": [], + "label": "verbose", + "description": [], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verbose.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verbose.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.success", + "type": "Function", + "tags": [], + "label": "success", + "description": [], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.success.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.success.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.step", + "type": "Function", + "tags": [], + "label": "step", + "description": [], + "signature": [ + "(name: string, desc: string | ", + "Symbol", + " | ", + "Node", + " | null, block: () => T) => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.step.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.step.$2", + "type": "CompoundType", + "tags": [], + "label": "desc", + "description": [], + "signature": [ + "string | ", + "Symbol", + " | ", + "Node", + " | null" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.step.$3", + "type": "Function", + "tags": [], + "label": "block", + "description": [], + "signature": [ + "() => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verboseStep", + "type": "Function", + "tags": [], + "label": "verboseStep", + "description": [], + "signature": [ + "(name: string, desc: string | ", + "Symbol", + " | ", + "Node", + " | null, block: () => T) => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verboseStep.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verboseStep.$2", + "type": "CompoundType", + "tags": [], + "label": "desc", + "description": [], + "signature": [ + "string | ", + "Symbol", + " | ", + "Node", + " | null" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliLog.verboseStep.$3", + "type": "Function", + "tags": [], + "label": "block", + "description": [], + "signature": [ + "() => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap", + "type": "Class", + "tags": [], + "label": "SetMap", + "description": [ + "\nA class for collecting items (V) based on some key (K)" + ], + "signature": [ + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.SetMap", + "text": "SetMap" + }, + "" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.has", + "type": "Function", + "tags": [], + "label": "has", + "description": [ + "\nIs there a group for the `key`?" + ], + "signature": [ + "(key: K) => boolean" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.has.$1", + "type": "Uncategorized", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "K" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.add", + "type": "Function", + "tags": [], + "label": "add", + "description": [ + "\nAdd a value to the group with `key`, if the group doesn't exist\nyet it is created." + ], + "signature": [ + "(key: K, value: V) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.add.$1", + "type": "Uncategorized", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "K" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.add.$2", + "type": "Uncategorized", + "tags": [], + "label": "value", + "description": [], + "signature": [ + "V" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.get", + "type": "Function", + "tags": [], + "label": "get", + "description": [ + "\nGet the group for the `key`, if the group doesn't exist then\n`undefined` is returned." + ], + "signature": [ + "(key: K) => Set | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.get.$1", + "type": "Uncategorized", + "tags": [], + "label": "key", + "description": [], + "signature": [ + "K" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.SetMap.values", + "type": "Function", + "tags": [], + "label": "values", + "description": [ + "\nReturns an iterator for the [K, V] entries stored in the SetMap" + ], + "signature": [ + "() => IterableIterator>" + ], + "path": "packages/kbn-type-summarizer-core/src/set_map.ts", + "deprecated": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.TestLog", + "type": "Class", + "tags": [], + "label": "TestLog", + "description": [ + "\nLogger which collects messages in memory for testing" + ], + "signature": [ + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.TestLog", + "text": "TestLog" + }, + " extends ", + { + "pluginId": "@kbn/type-summarizer-core", + "scope": "server", + "docId": "kibKbnTypeSummarizerCorePluginApi", + "section": "def-server.CliLog", + "text": "CliLog" + } + ], + "path": "packages/kbn-type-summarizer-core/src/log/test_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.TestLog.messages", + "type": "Array", + "tags": [], + "label": "messages", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/test_log.ts", + "deprecated": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.TestLog.Unnamed", + "type": "Function", + "tags": [], + "label": "Constructor", + "description": [], + "signature": [ + "any" + ], + "path": "packages/kbn-type-summarizer-core/src/log/test_log.ts", + "deprecated": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.describeNode", + "type": "Function", + "tags": [], + "label": "describeNode", + "description": [ + "\nTurn a Node instance into a string which describes the type, name, filename, and position of the node" + ], + "signature": [ + "(node: ", + "Node", + ", cwd: string | undefined) => string" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.describeNode.$1", + "type": "Object", + "tags": [], + "label": "node", + "description": [], + "signature": [ + "Node" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.describeNode.$2", + "type": "string", + "tags": [], + "label": "cwd", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.describeSymbol", + "type": "Function", + "tags": [], + "label": "describeSymbol", + "description": [ + "\nGet a human readable string describing a symbol, requires that symbols have a declaration\nwhich will be passed to describeNode()" + ], + "signature": [ + "(symbol: ", + "Symbol", + ", cwd: string | undefined) => string" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.describeSymbol.$1", + "type": "Object", + "tags": [], + "label": "symbol", + "description": [], + "signature": [ + "Symbol" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.describeSymbol.$2", + "type": "string", + "tags": [], + "label": "cwd", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.getKindName", + "type": "Function", + "tags": [], + "label": "getKindName", + "description": [ + "\nGet a human readable name of the syntax \"kind\". TS nodes use enums for their \"kind\" field\nwhich makes it tricky to know what you're looking at, and the `ts.SyntaxKind` map is lossy\nbecause many enum members have the same numeric value. To get around this we convert the\nts.SyntaxKind map into a `SetMap` which puts all the syntax kind names for a given number\ninto a set and allows us to report all possible type names from `getKindName()`" + ], + "signature": [ + "(node: ", + "Node", + ") => string" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.getKindName.$1", + "type": "Object", + "tags": [], + "label": "node", + "description": [], + "signature": [ + "Node" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.hasIdentifierName", + "type": "Function", + "tags": [], + "label": "hasIdentifierName", + "description": [ + "\nDoes this AST node have a name which is an identifier?" + ], + "signature": [ + "(node: any) => boolean" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.hasIdentifierName.$1", + "type": "Any", + "tags": [], + "label": "node", + "description": [], + "signature": [ + "any" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.isAliasSymbol", + "type": "Function", + "tags": [], + "label": "isAliasSymbol", + "description": [ + "\nIs this symbol pointing to another symbol?" + ], + "signature": [ + "(symbol: ", + "Symbol", + ") => boolean" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.isAliasSymbol.$1", + "type": "Object", + "tags": [], + "label": "symbol", + "description": [], + "signature": [ + "Symbol" + ], + "path": "packages/kbn-type-summarizer-core/src/ts_helpers.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.isSystemError", + "type": "Function", + "tags": [], + "label": "isSystemError", + "description": [ + "\nIs this error instance a Node.js system error which has an error code attached?" + ], + "signature": [ + "(error: Error) => boolean" + ], + "path": "packages/kbn-type-summarizer-core/src/error.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.isSystemError.$1", + "type": "Object", + "tags": [], + "label": "error", + "description": [], + "signature": [ + "Error" + ], + "path": "packages/kbn-type-summarizer-core/src/error.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.parseJson", + "type": "Function", + "tags": [], + "label": "parseJson", + "description": [ + "\nParse JSON, but thrown a more meaningful error message when parsing fails" + ], + "signature": [ + "(json: string, from: string | undefined) => any" + ], + "path": "packages/kbn-type-summarizer-core/src/json.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.parseJson.$1", + "type": "string", + "tags": [], + "label": "json", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/json.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.parseJson.$2", + "type": "string", + "tags": [], + "label": "from", + "description": [], + "signature": [ + "string | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/json.ts", + "deprecated": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.toError", + "type": "Function", + "tags": [], + "label": "toError", + "description": [ + "\nConvert an unknown thrown value to an Error instance if it isn't alread" + ], + "signature": [ + "(thrown: unknown) => Error" + ], + "path": "packages/kbn-type-summarizer-core/src/error.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.toError.$1", + "type": "Unknown", + "tags": [], + "label": "thrown", + "description": [], + "signature": [ + "unknown" + ], + "path": "packages/kbn-type-summarizer-core/src/error.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.tryReadFile", + "type": "Function", + "tags": [], + "label": "tryReadFile", + "description": [ + "\nRead a file, if the file doesn't exist return undefined. If any other\nerror occurs they will be thrown." + ], + "signature": [ + "(path: string, encoding: \"utf8\" | \"utf-8\") => Promise" + ], + "path": "packages/kbn-type-summarizer-core/src/fs.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.tryReadFile.$1", + "type": "string", + "tags": [], + "label": "path", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/fs.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.tryReadFile.$2", + "type": "CompoundType", + "tags": [], + "label": "encoding", + "description": [], + "signature": [ + "\"utf8\" | \"utf-8\"" + ], + "path": "packages/kbn-type-summarizer-core/src/fs.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.tryReadFile", + "type": "Function", + "tags": [], + "label": "tryReadFile", + "description": [ + "\nRead a file, if the file doesn't exist return undefined. If any other\nerror occurs they will be thrown." + ], + "signature": [ + "(path: string, encoding: BufferEncoding | undefined) => Promise" + ], + "path": "packages/kbn-type-summarizer-core/src/fs.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.tryReadFile.$1", + "type": "string", + "tags": [], + "label": "path", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/fs.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.tryReadFile.$2", + "type": "CompoundType", + "tags": [], + "label": "encoding", + "description": [], + "signature": [ + "BufferEncoding | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/fs.ts", + "deprecated": false, + "isRequired": false + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliErrorOptions", + "type": "Interface", + "tags": [], + "label": "CliErrorOptions", + "description": [ + "\nOptions for customizing CliError instances" + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliErrorOptions.exitCode", + "type": "number", + "tags": [], + "label": "exitCode", + "description": [], + "signature": [ + "number | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.CliErrorOptions.showHelp", + "type": "CompoundType", + "tags": [], + "label": "showHelp", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/kbn-type-summarizer-core/src/cli_error.ts", + "deprecated": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger", + "type": "Interface", + "tags": [], + "label": "Logger", + "description": [ + "\nLogger interface used by @kbn/type-summarizer-* packages" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.info", + "type": "Function", + "tags": [], + "label": "info", + "description": [ + "\nWrite a message to the log with the level \"info\"" + ], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.info.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [ + "any message" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.info.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [ + "any serializeable values you would like to be appended to the log message" + ], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.warn", + "type": "Function", + "tags": [], + "label": "warn", + "description": [ + "\nWrite a message to the log with the level \"warn\"" + ], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.warn.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [ + "any message" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.warn.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [ + "any serializeable values you would like to be appended to the log message" + ], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.error", + "type": "Function", + "tags": [], + "label": "error", + "description": [ + "\nWrite a message to the log with the level \"error\"" + ], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.error.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [ + "any message" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.error.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [ + "any serializeable values you would like to be appended to the log message" + ], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.debug", + "type": "Function", + "tags": [], + "label": "debug", + "description": [ + "\nWrite a message to the log with the level \"debug\"" + ], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.debug.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [ + "any message" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.debug.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [ + "any serializeable values you would like to be appended to the log message" + ], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verbose", + "type": "Function", + "tags": [], + "label": "verbose", + "description": [ + "\nWrite a message to the log with the level \"verbose\"" + ], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verbose.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [ + "any message" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verbose.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [ + "any serializeable values you would like to be appended to the log message" + ], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.success", + "type": "Function", + "tags": [], + "label": "success", + "description": [ + "\nWrite a message to the log, only excluded in silent mode" + ], + "signature": [ + "(msg: string, ...args: any[]) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.success.$1", + "type": "string", + "tags": [], + "label": "msg", + "description": [ + "any message" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.success.$2", + "type": "Array", + "tags": [], + "label": "args", + "description": [ + "any serializeable values you would like to be appended to the log message" + ], + "signature": [ + "any[]" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.step", + "type": "Function", + "tags": [], + "label": "step", + "description": [ + "\nWrite a message to the log indicating the beginning of a step, then run the passed\nblock, any log output produced inside that step will be indented and at the end the\nduration of the step will be written. If the log level is below verbose then any\n\"verbose steps\" executed inside this step will be summaried by this step at the end\nas well." + ], + "signature": [ + "(name: string, desc: string | ", + "Symbol", + " | ", + "Node", + " | null, block: () => T) => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.step.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "a common name for steps of a specific type" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.step.$2", + "type": "CompoundType", + "tags": [], + "label": "desc", + "description": [ + "a specific name to describe the unique information about this step" + ], + "signature": [ + "string | ", + "Symbol", + " | ", + "Node", + " | null" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.step.$3", + "type": "Function", + "tags": [], + "label": "block", + "description": [ + "the function body which defines this step" + ], + "signature": [ + "() => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verboseStep", + "type": "Function", + "tags": [], + "label": "verboseStep", + "description": [ + "\nJust like step(), except that unless the logging level is set to verbose the steps with\nthe same name will be summaried at the end of the containing step, rather than logged\ndirectly." + ], + "signature": [ + "(name: string, desc: string | ", + "Symbol", + " | ", + "Node", + " | null, block: () => T) => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verboseStep.$1", + "type": "string", + "tags": [], + "label": "name", + "description": [ + "a common name for steps of a specific type" + ], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verboseStep.$2", + "type": "CompoundType", + "tags": [], + "label": "desc", + "description": [ + "a specific name to describe the unique information about this step" + ], + "signature": [ + "string | ", + "Symbol", + " | ", + "Node", + " | null" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.Logger.verboseStep.$3", + "type": "Function", + "tags": [], + "label": "block", + "description": [ + "the function body which defines this step" + ], + "signature": [ + "() => T" + ], + "path": "packages/kbn-type-summarizer-core/src/log/logger.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.LogWriter", + "type": "Interface", + "tags": [], + "label": "LogWriter", + "description": [ + "\nInterface of objects which receive log messages, often times points to stdout, but\nreplaced with a log message collector in tests" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.LogWriter.write", + "type": "Function", + "tags": [], + "label": "write", + "description": [], + "signature": [ + "(chunk: string) => void" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.LogWriter.write.$1", + "type": "string", + "tags": [], + "label": "chunk", + "description": [], + "signature": [ + "string" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/type-summarizer-core", + "id": "def-server.LogLevel", + "type": "Type", + "tags": [], + "label": "LogLevel", + "description": [ + "\nLevel that the logger is running at, any message logged \"above\" this level will be dropped" + ], + "signature": [ + "\"info\" | \"debug\" | \"silent\" | \"verbose\" | \"quiet\"" + ], + "path": "packages/kbn-type-summarizer-core/src/log/cli_log.ts", + "deprecated": false, + "initialIsOpen": false + } + ], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx new file mode 100644 index 0000000000000..8dbfd96f29b8e --- /dev/null +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -0,0 +1,36 @@ +--- +id: kibKbnTypeSummarizerCorePluginApi +slug: /kibana-dev-docs/api/kbn-type-summarizer-core +title: "@kbn/type-summarizer-core" +image: https://source.unsplash.com/400x175/?github +summary: API docs for the @kbn/type-summarizer-core plugin +date: 2022-07-07 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] +warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. +--- +import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 113 | 1 | 65 | 0 | + +## Server + +### Functions + + +### Classes + + +### Interfaces + + +### Consts, variables and types + + diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 808fde2273983..3233fa76a2e00 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/typed-react-router-config plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 67aadc6dc4949..17a226b3c8216 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/ui-theme plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 6f6d672dbfe6c..379422a85f923 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/utility-types plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index dc57ff2d642f9..0366f0dc1112a 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/utility-types-jest plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index d662d10ef9331..880be2fd505a3 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github summary: API docs for the @kbn/utils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 4e05c25956716..57f50bd1ca583 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github summary: API docs for the kibanaOverview plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 1786cc7e1b8b9..e93de887790f0 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github summary: API docs for the kibanaReact plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index d4efa5c9c12cd..199132056be95 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github summary: API docs for the kibanaUtils plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index e740547d9ec8d..bd15393b522d3 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github summary: API docs for the kubernetesSecurity plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/lens.devdocs.json b/api_docs/lens.devdocs.json index 98529c430485a..0b273638bd947 100644 --- a/api_docs/lens.devdocs.json +++ b/api_docs/lens.devdocs.json @@ -3666,7 +3666,15 @@ "label": "params", "description": [], "signature": [ - "{ size: number; accuracyMode?: boolean | undefined; orderBy: { type: \"alphabetical\"; fallback?: boolean | undefined; } | { type: \"rare\"; maxDocCount: number; } | { type: \"column\"; columnId: string; }; orderDirection: \"asc\" | \"desc\"; otherBucket?: boolean | undefined; missingBucket?: boolean | undefined; secondaryFields?: string[] | undefined; format?: ", + "{ size: number; accuracyMode?: boolean | undefined; orderBy: { type: \"alphabetical\"; fallback?: boolean | undefined; } | { type: \"rare\"; maxDocCount: number; } | { type: \"column\"; columnId: string; } | { type: \"custom\"; }; orderAgg?: ", + { + "pluginId": "lens", + "scope": "public", + "docId": "kibLensPluginApi", + "section": "def-public.FieldBasedIndexPatternColumn", + "text": "FieldBasedIndexPatternColumn" + }, + " | undefined; orderDirection: \"asc\" | \"desc\"; otherBucket?: boolean | undefined; missingBucket?: boolean | undefined; secondaryFields?: string[] | undefined; format?: ", "ValueFormatConfig", " | undefined; parentFormat?: { id: string; } | undefined; }" ], diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 90073104e1837..42ad81a626cf4 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github summary: API docs for the lens plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/license_api_guard.devdocs.json b/api_docs/license_api_guard.devdocs.json index ea6f7bcd81800..bb064fc65b964 100644 --- a/api_docs/license_api_guard.devdocs.json +++ b/api_docs/license_api_guard.devdocs.json @@ -100,7 +100,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, @@ -516,7 +516,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 188fb92655655..1ee79c0b82120 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github summary: API docs for the licenseApiGuard plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index d9d715e1dd54e..7575ab1fb03f3 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the licenseManagement plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/licensing.devdocs.json b/api_docs/licensing.devdocs.json index 3e7f24385561c..a9b63f1e5b5ea 100644 --- a/api_docs/licensing.devdocs.json +++ b/api_docs/licensing.devdocs.json @@ -911,7 +911,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, @@ -1105,7 +1105,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, @@ -1331,7 +1331,7 @@ { "pluginId": "core", "scope": "server", - "docId": "kibCoreHttpPluginApi", + "docId": "kibCorePluginApi", "section": "def-server.RequestHandler", "text": "RequestHandler" }, diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index 5a06cb5ece3f8..408a1f1b9566c 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github summary: API docs for the licensing plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index a0fe3a48add5f..d9b5b1a9ddddc 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github summary: API docs for the lists plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 4e5486368b8f8..e306ab4cdf862 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github summary: API docs for the management plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index c0eaf26d90d59..2d87ac6b79aa2 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github summary: API docs for the maps plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 1b0953f07fd2f..22d3d63f0e37a 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github summary: API docs for the mapsEms plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index ae1cebce4ed8f..168688b0df2eb 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github summary: API docs for the ml plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index c8e49f240a2db..06c5a9c9633d3 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github summary: API docs for the monitoring plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 5d839a40cdde2..b6e9f6b947d56 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github summary: API docs for the monitoringCollection plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 98c82a538a92f..eb28eb1c8d223 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github summary: API docs for the navigation plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index c3fa1c1fba316..380c1274bd58c 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github summary: API docs for the newsfeed plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index c385f2eed66b4..3bcd908b14782 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -7138,7 +7138,9 @@ }, ">>[TEndpoint] extends ", "ServerRoute", - " ? TReturnType : never" + " ? TReturnType : never" ], "path": "x-pack/plugins/observability/server/routes/types.ts", "deprecated": false, diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index f180f03f5341e..66c0174194596 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github summary: API docs for the observability plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index f923d8259a065..e61edd6521ce1 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github summary: API docs for the osquery plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index b9cc3c43f28e7..9660346ea7e93 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -3,7 +3,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory summary: Directory of public APIs available through plugins or packages. -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -12,13 +12,13 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 320 | 263 | 36 | +| 323 | 265 | 36 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 26667 | 172 | 19266 | 1279 | +| 26796 | 173 | 19359 | 1322 | ## Plugin Directory @@ -38,7 +38,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 6 | 0 | 6 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 206 | 0 | 198 | 7 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2525 | 15 | 914 | 28 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2546 | 15 | 926 | 28 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 101 | 0 | 82 | 1 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 143 | 0 | 141 | 12 | @@ -75,7 +75,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 222 | 0 | 95 | 2 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Index pattern fields and ambiguous values formatters | 288 | 5 | 249 | 3 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | The file upload plugin contains components and services for uploading a file, analyzing its data, and then importing the data into an Elasticsearch index. Supported file types include CSV, TSV, newline-delimited JSON and GeoJSON. | 62 | 0 | 62 | 2 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1504 | 8 | 1373 | 10 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1507 | 8 | 1376 | 10 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 68 | 0 | 14 | 5 | | globalSearchBar | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | @@ -141,7 +141,7 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 452 | 1 | 347 | 35 | | | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | -| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 388 | 0 | 374 | 36 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 397 | 0 | 383 | 37 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 130 | 0 | 91 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 205 | 0 | 142 | 9 | | | [Unified Search](https://github.com/orgs/elastic/teams/kibana-app-services) | Contains all the key functionality of Kibana's unified search experience.Contains all the key functionality of Kibana's unified search experience. | 82 | 2 | 78 | 13 | @@ -172,7 +172,8 @@ warning: This document is auto-generated and is meant to be viewed inside our ex |--------------|----------------|-----------|--------------|----------|---------------|--------| | | [Owner missing] | Elastic APM trace data generator | 74 | 0 | 74 | 11 | | | [Owner missing] | - | 11 | 5 | 11 | 0 | -| | Machine Learning UI | Static utilities for AIOps related efforts. | 48 | 0 | 26 | 0 | +| | Machine Learning UI | React components for AIOps related efforts. | 6 | 0 | 6 | 0 | +| | Machine Learning UI | Static utilities for AIOps related efforts. | 46 | 0 | 24 | 0 | | | [Owner missing] | Alerts components and hooks | 9 | 1 | 9 | 0 | | | Ahmad Bamieh ahmadbamieh@gmail.com | Kibana Analytics tool | 69 | 0 | 69 | 2 | | | [Owner missing] | - | 96 | 0 | 0 | 0 | @@ -305,7 +306,8 @@ warning: This document is auto-generated and is meant to be viewed inside our ex | | Operations | - | 246 | 5 | 207 | 9 | | | [Owner missing] | - | 135 | 8 | 103 | 2 | | | [Owner missing] | - | 72 | 0 | 55 | 0 | -| | [Owner missing] | - | 29 | 0 | 2 | 0 | +| | [Owner missing] | - | 8 | 0 | 2 | 0 | +| | [Owner missing] | - | 113 | 1 | 65 | 0 | | | [Owner missing] | - | 83 | 0 | 83 | 1 | | | [Owner missing] | - | 7 | 0 | 6 | 0 | | | [Owner missing] | - | 29 | 0 | 10 | 1 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index 92bc063049bcf..615641a446063 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github summary: API docs for the presentationUtil plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 114eef16826a7..6267ed4a78d0a 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github summary: API docs for the remoteClusters plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 5bf26cf8c1721..4d0310f45abde 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github summary: API docs for the reporting plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index b6569d479f026..a6462e7d5e7a6 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github summary: API docs for the rollup plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 88c351262977d..9b0fb57425957 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github summary: API docs for the ruleRegistry plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 6156533b032f8..f248cad1afc8c 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github summary: API docs for the runtimeFields plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 05e031e0e731a..09f4621da2c13 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjects plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 5d61a85adfc4e..30a39be133c0d 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjectsManagement plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 62bec6a292c6c..739b36ba9d307 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjectsTagging plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index bec2ae79a9dcf..f98e1eac9c4d7 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github summary: API docs for the savedObjectsTaggingOss plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 1592f917fca4b..8ff8e08074915 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github summary: API docs for the screenshotMode plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 09ddcae9e441c..a0b0e8ae0a738 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github summary: API docs for the screenshotting plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/security.mdx b/api_docs/security.mdx index 273af482077ce..c3f420c1b4d7c 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github summary: API docs for the security plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/security_solution.devdocs.json b/api_docs/security_solution.devdocs.json index c2add516dfb24..fb3cb3c38c48e 100644 --- a/api_docs/security_solution.devdocs.json +++ b/api_docs/security_solution.devdocs.json @@ -62,7 +62,7 @@ "label": "experimentalFeatures", "description": [], "signature": [ - "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly groupedNavigation: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; }" + "{ readonly tGridEnabled: boolean; readonly tGridEventRenderedViewEnabled: boolean; readonly excludePoliciesInFilterEnabled: boolean; readonly kubernetesEnabled: boolean; readonly disableIsolationUIPendingStatuses: boolean; readonly riskyHostsEnabled: boolean; readonly riskyUsersEnabled: boolean; readonly pendingActionResponsesWithAck: boolean; readonly policyListEnabled: boolean; readonly policyResponseInFleetEnabled: boolean; readonly groupedNavigation: boolean; readonly previewTelemetryUrlEnabled: boolean; readonly responseActionsConsoleEnabled: boolean; readonly cloudSecurityPostureNavigation: boolean; }" ], "path": "x-pack/plugins/security_solution/public/plugin.tsx", "deprecated": false @@ -1015,7 +1015,7 @@ "label": "ConfigType", "description": [], "signature": [ - "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; riskyHostsEnabled: boolean; riskyUsersEnabled: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; groupedNavigation: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; }>; }" + "Readonly<{} & { signalsIndex: string; maxRuleImportExportSize: number; maxRuleImportPayloadBytes: number; maxTimelineImportExportSize: number; maxTimelineImportPayloadBytes: number; alertMergeStrategy: \"allFields\" | \"missingFields\" | \"noFields\"; alertIgnoreFields: string[]; enableExperimental: string[]; packagerTaskInterval: string; prebuiltRulesFromFileSystem: boolean; prebuiltRulesFromSavedObjects: boolean; }> & { experimentalFeatures: Readonly<{ tGridEnabled: boolean; tGridEventRenderedViewEnabled: boolean; excludePoliciesInFilterEnabled: boolean; kubernetesEnabled: boolean; disableIsolationUIPendingStatuses: boolean; riskyHostsEnabled: boolean; riskyUsersEnabled: boolean; pendingActionResponsesWithAck: boolean; policyListEnabled: boolean; policyResponseInFleetEnabled: boolean; groupedNavigation: boolean; previewTelemetryUrlEnabled: boolean; responseActionsConsoleEnabled: boolean; cloudSecurityPostureNavigation: boolean; }>; }" ], "path": "x-pack/plugins/security_solution/server/config.ts", "deprecated": false, diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 6c5d5c0f02248..6dd1a95283887 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github summary: API docs for the securitySolution plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index 9881dbce1cd89..be484e389ad1e 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github summary: API docs for the sessionView plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/share.mdx b/api_docs/share.mdx index 51e447f38ed1b..e5cfdc2a4e5a9 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github summary: API docs for the share plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/shared_u_x.mdx b/api_docs/shared_u_x.mdx index e879fa0cd9a1a..55f9e09e9cb18 100644 --- a/api_docs/shared_u_x.mdx +++ b/api_docs/shared_u_x.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/sharedUX title: "sharedUX" image: https://source.unsplash.com/400x175/?github summary: API docs for the sharedUX plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sharedUX'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index da01f4c730655..b92c1c43fb03f 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github summary: API docs for the snapshotRestore plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 53fa9bf08e176..d02c63a83c8ea 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github summary: API docs for the spaces plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index 98e301634a2da..64461dd5c4028 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github summary: API docs for the stackAlerts plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 448d0b8cd40c6..d2b9ce3ab3839 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github summary: API docs for the taskManager plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 6b439649168fd..0854e50f3825b 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetry plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index 949d390faf840..acf2babed4e4f 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetryCollectionManager plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index b57243cec36d5..b132a4c62b176 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetryCollectionXpack plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index c21a6ed5b29c0..7c7cd8b7c61d7 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github summary: API docs for the telemetryManagementSection plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 744b2fbcec9af..5160195c9df7c 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github summary: API docs for the timelines plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index f34cd424cf0f5..2cebd6090ddfb 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github summary: API docs for the transform plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 1da9a4c76258e..cee734b09333f 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -2778,6 +2778,143 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.RuleDefinitionProps", + "type": "Interface", + "tags": [], + "label": "RuleDefinitionProps", + "description": [], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.RuleDefinitionProps.rule", + "type": "CompoundType", + "tags": [], + "label": "rule", + "description": [], + "signature": [ + "Omit<", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.SanitizedRule", + "text": "SanitizedRule" + }, + "<", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">, \"alertTypeId\"> & { ruleTypeId: string; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.RuleDefinitionProps.ruleTypeRegistry", + "type": "Object", + "tags": [], + "label": "ruleTypeRegistry", + "description": [], + "signature": [ + "{ list: () => ", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.RuleTypeModel", + "text": "RuleTypeModel" + }, + "<", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">[]; get: (id: string) => ", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.RuleTypeModel", + "text": "RuleTypeModel" + }, + "<", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">; register: (objectType: ", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.RuleTypeModel", + "text": "RuleTypeModel" + }, + "<", + { + "pluginId": "alerting", + "scope": "common", + "docId": "kibAlertingPluginApi", + "section": "def-common.RuleTypeParams", + "text": "RuleTypeParams" + }, + ">) => void; has: (id: string) => boolean; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.RuleDefinitionProps.actionTypeRegistry", + "type": "Object", + "tags": [], + "label": "actionTypeRegistry", + "description": [], + "signature": [ + "{ list: () => ", + "ActionTypeModel", + "[]; get: (id: string) => ", + "ActionTypeModel", + "; register: (objectType: ", + "ActionTypeModel", + ") => void; has: (id: string) => boolean; }" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.RuleDefinitionProps.onEditRule", + "type": "Function", + "tags": [], + "label": "onEditRule", + "description": [], + "signature": [ + "() => Promise" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.RuleTableItem", @@ -5615,6 +5752,92 @@ "deprecated": false, "children": [], "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRuleDefinition", + "type": "Function", + "tags": [], + "label": "getRuleDefinition", + "description": [], + "signature": [ + "(props: ", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.RuleDefinitionProps", + "text": "RuleDefinitionProps" + }, + ") => React.ReactElement<", + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.RuleDefinitionProps", + "text": "RuleDefinitionProps" + }, + ", string | React.JSXElementConstructor>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRuleDefinition.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + { + "pluginId": "triggersActionsUi", + "scope": "public", + "docId": "kibTriggersActionsUiPluginApi", + "section": "def-public.RuleDefinitionProps", + "text": "RuleDefinitionProps" + } + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRuleStatusPanel", + "type": "Function", + "tags": [], + "label": "getRuleStatusPanel", + "description": [], + "signature": [ + "(props: ", + "RuleStatusPanelProps", + ") => React.ReactElement<", + "RuleStatusPanelProps", + ", string | React.JSXElementConstructor>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "children": [ + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRuleStatusPanel.$1", + "type": "Object", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "RuleStatusPanelProps" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "isRequired": true + } + ], + "returnComment": [] } ], "lifecycle": "start", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 2cb9614269fd4..d0ddc106a7c7f 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github summary: API docs for the triggersActionsUi plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- @@ -18,7 +18,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 388 | 0 | 374 | 36 | +| 397 | 0 | 383 | 37 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index cbec216653b2d..34c476f00059e 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github summary: API docs for the uiActions plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 5c03955fb7367..db5d66222e121 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github summary: API docs for the uiActionsEnhanced plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 9288c722cc259..70c8f106a935d 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github summary: API docs for the unifiedSearch plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index fee8f8310b836..39fa7e112470f 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github summary: API docs for the unifiedSearch.autocomplete plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index f5c0bbb6e3efe..b44768bd49ecd 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github summary: API docs for the urlForwarding plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/usage_collection.devdocs.json b/api_docs/usage_collection.devdocs.json index 15737e2480fdf..244872495d1b3 100644 --- a/api_docs/usage_collection.devdocs.json +++ b/api_docs/usage_collection.devdocs.json @@ -2187,7 +2187,7 @@ "tags": [], "label": "AllowedSchemaTypes", "description": [ - "\r\nPossible type values in the schema" + "\nPossible type values in the schema" ], "signature": [ "\"boolean\" | \"keyword\" | \"text\" | \"date\" | \"long\" | \"double\" | \"short\" | \"float\" | \"integer\" | \"byte\"" diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 64974d8c08078..aa2955e6eb992 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github summary: API docs for the usageCollection plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 1707b405e951e..101efd10273e8 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github summary: API docs for the ux plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index 554c80deef0ea..168b7171bf3ba 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github summary: API docs for the visDefaultEditor plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index b5337b23ac2e7..5be5c3224ffe3 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeGauge plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 9e513c3157782..2f3cb6a69ff12 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeHeatmap plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index a13ccefef6faa..9ddb0368abab6 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypePie plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 4645b7b438437..25d09d2ce1128 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeTable plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 9a8052f329002..5da18f356df4d 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeTimelion plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 8e5a1eba4b82a..041fe92d0a015 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeTimeseries plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 1e381c7034dae..a038825f10e28 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeVega plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 5d542fb21a302..9a696fc35ba41 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeVislib plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 4f465b157ddb8..5dfc27825b615 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github summary: API docs for the visTypeXy plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/api_docs/visualizations.devdocs.json b/api_docs/visualizations.devdocs.json index fbd2d14562567..e6d13ebfd5089 100644 --- a/api_docs/visualizations.devdocs.json +++ b/api_docs/visualizations.devdocs.json @@ -3234,7 +3234,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined; createRelativeFilter: (indexPattern: ", + " | ", + "MatchAllRangeFilter", + " | undefined; createRelativeFilter: (indexPattern: ", { "pluginId": "dataViews", "scope": "common", @@ -3248,7 +3250,9 @@ "RangeFilter", " | ", "ScriptedRangeFilter", - " | MatchAllRangeFilter | undefined; getBounds: () => ", + " | ", + "MatchAllRangeFilter", + " | undefined; getBounds: () => ", { "pluginId": "data", "scope": "common", diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 24091ec1ffde2..640015378209b 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -4,7 +4,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github summary: API docs for the visualizations plugin -date: 2022-07-05 +date: 2022-07-07 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. --- diff --git a/docs/api/cases/cases-api-get-tags.asciidoc b/docs/api/cases/cases-api-get-tags.asciidoc index 5008411f37bdb..aea18291abd59 100644 --- a/docs/api/cases/cases-api-get-tags.asciidoc +++ b/docs/api/cases/cases-api-get-tags.asciidoc @@ -28,9 +28,10 @@ default space is used. === {api-query-parms-title} `owner`:: -(Optional, string or array of strings) A filter to limit the retrieved tags to a specific set of applications. -Valid values are: `cases`, `observability`, and `securitySolution`. If this parameter is omitted, the response -contains tags from all cases that the user has access to read. +(Optional, string or array of strings) A filter to limit the retrieved tags to a +specific set of applications. Valid values are: `cases`, `observability`, and +`securitySolution`. If this parameter is omitted, the response contains tags +from all cases that the user has access to read. === {api-response-codes-title} @@ -45,14 +46,15 @@ GET api/cases/tags -------------------------------------------------- // KIBANA -The API returns a JSON object with tags from all the cases that the user has access to read. For example: +The API returns a JSON object with tags from all the cases that the user has +access to read. For example: [source,json] -------------------------------------------------- [ - "windows", - "phishing", - "social engineering", - "bubblegum" + "observability", + "security", + "tag 1", + "tag 2" ] -------------------------------------------------- diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index b667aaf011de2..041c0cee57359 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -508,7 +508,7 @@ the infrastructure monitoring use-case within Kibana. |{kib-repo}blob/{branch}/x-pack/plugins/lens/readme.md[lens] -|Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. +|Lens is a visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. |{kib-repo}blob/{branch}/x-pack/plugins/license_api_guard/README.md[licenseApiGuard] diff --git a/docs/osquery/osquery-faq.asciidoc b/docs/osquery/osquery-faq.asciidoc index 0c353474a51c6..e65e520f0becf 100644 --- a/docs/osquery/osquery-faq.asciidoc +++ b/docs/osquery/osquery-faq.asciidoc @@ -57,7 +57,7 @@ https://osquery.readthedocs.io/en/stable/deployment/extensions/[Osquery extensio Yes, you can set up https://osquery.readthedocs.io/en/stable/deployment/file-integrity-monitoring/[Osquery FIM] using the Advanced configuration option for Osquery Manager (see <>). -However, Elastic also provides a File Integrity Monitoring integration for Elastic Agent, which might prove +However, Elastic also provides a https://docs.elastic.co/en/integrations/fim[File Integrity Monitoring] integration for Elastic Agent, which might prove to be easier to configure than the current options available for Osquery Manager. [float] diff --git a/docs/osquery/osquery.asciidoc b/docs/osquery/osquery.asciidoc index a068755a81454..88f8f768df0de 100644 --- a/docs/osquery/osquery.asciidoc +++ b/docs/osquery/osquery.asciidoc @@ -134,13 +134,13 @@ Once you save a query, you can only edit it from the *Saved queries* tab: . Go to *Saved queries*, and then click **Add saved query** or the edit icon. . Provide the following fields: -* The unique identifier. +* The unique identifier (required). * A brief description. -* The SQL query. +* The SQL query (required). Osquery supports multi-line queries. -* The <> to populate when the query is run. These fields are also copied in when you add this query to a pack. +* The <> to populate when the query is run (optional). These fields are also copied in when you add this query to a pack. * The defaults to set when you add the query to a pack. diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index d706526730459..88fc015ac8b9a 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -69,7 +69,7 @@ If capturing a report fails for any reason, {kib} will re-queue the report job f `xpack.reporting.queue.indexInterval`:: How often the index that stores reporting jobs rolls over to a new index. Valid values are `year`, `month`, `week`, `day`, and `hour`. Defaults to `week`. -[[xpack-reportingQueue-pollEnabled]] `xpack.reporting.queue.pollEnabled` {ess-icon}:: +[[xpack-reportingQueue-pollEnabled]] `xpack.reporting.queue.pollEnabled` :: When `true`, enables the {kib} instance to poll {es} for pending jobs and claim them for execution. When `false`, allows the {kib} instance to only add new jobs to the reporting queue, list jobs, and provide the downloads to completed reports through the UI. This requires a deployment where at least diff --git a/package.json b/package.json index ef7ff5c382c15..f6e1ac67dc0f0 100644 --- a/package.json +++ b/package.json @@ -112,10 +112,10 @@ "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "1.2.1", "@elastic/numeral": "^2.5.1", - "@elastic/react-search-ui": "^1.6.0", + "@elastic/react-search-ui": "^1.14.0", "@elastic/request-crypto": "2.0.1", "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set", - "@elastic/search-ui-app-search-connector": "^1.6.0", + "@elastic/search-ui-app-search-connector": "^1.14.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.9.0", "@emotion/react": "^11.9.0", @@ -129,6 +129,7 @@ "@hapi/inert": "^6.0.4", "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:bazel-bin/packages/kbn-ace", + "@kbn/aiops-components": "link:bazel-bin/x-pack/packages/ml/aiops_components", "@kbn/aiops-utils": "link:bazel-bin/x-pack/packages/ml/aiops_utils", "@kbn/alerts": "link:bazel-bin/packages/kbn-alerts", "@kbn/analytics": "link:bazel-bin/packages/kbn-analytics", @@ -299,11 +300,14 @@ "cytoscape-dagre": "^2.2.2", "d3": "3.5.17", "d3-array": "1.2.4", + "d3-brush": "^3.0.0", "d3-cloud": "1.2.5", "d3-interpolate": "^3.0.1", "d3-scale": "^2.2.2", + "d3-selection": "^3.0.0", "d3-shape": "^1.1.0", "d3-time": "^1.1.0", + "d3-transition": "^3.0.1", "dedent": "^0.7.0", "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", @@ -372,7 +376,7 @@ "mime": "^2.4.4", "mime-types": "^2.1.27", "minimatch": "^3.1.2", - "moment": "^2.29.2", + "moment": "^2.29.4", "moment-duration-format": "^2.3.2", "moment-timezone": "^0.5.27", "monaco-editor": "^0.22.3", @@ -571,6 +575,8 @@ "@kbn/test-subj-selector": "link:bazel-bin/packages/kbn-test-subj-selector", "@kbn/tooling-log": "link:bazel-bin/packages/kbn-tooling-log", "@kbn/type-summarizer": "link:bazel-bin/packages/kbn-type-summarizer", + "@kbn/type-summarizer-cli": "link:bazel-bin/packages/kbn-type-summarizer-cli", + "@kbn/type-summarizer-core": "link:bazel-bin/packages/kbn-type-summarizer-core", "@loaders.gl/polyfills": "^2.3.5", "@mapbox/vector-tile": "1.3.1", "@microsoft/api-documenter": "7.13.68", @@ -615,11 +621,14 @@ "@types/cytoscape": "^3.14.0", "@types/d3": "^3.5.43", "@types/d3-array": "^1.2.7", + "@types/d3-brush": "^3.0.0", "@types/d3-interpolate": "^2.0.0", "@types/d3-scale": "^2.2.6", + "@types/d3-selection": "^3.0.0", "@types/d3-shape": "^1.3.1", "@types/d3-time": "^1.0.10", "@types/d3-time-format": "^2.1.1", + "@types/d3-transition": "^3.0.1", "@types/dedent": "^0.7.0", "@types/deep-freeze-strict": "^1.1.0", "@types/delete-empty": "^2.0.0", @@ -664,6 +673,7 @@ "@types/json5": "^0.0.30", "@types/jsonwebtoken": "^8.5.6", "@types/kbn__ace": "link:bazel-bin/packages/kbn-ace/npm_module_types", + "@types/kbn__aiops-components": "link:bazel-bin/x-pack/packages/ml/aiops_components/npm_module_types", "@types/kbn__aiops-utils": "link:bazel-bin/x-pack/packages/ml/aiops_utils/npm_module_types", "@types/kbn__alerts": "link:bazel-bin/packages/kbn-alerts/npm_module_types", "@types/kbn__analytics": "link:bazel-bin/packages/kbn-analytics/npm_module_types", @@ -815,6 +825,9 @@ "@types/kbn__test": "link:bazel-bin/packages/kbn-test/npm_module_types", "@types/kbn__test-jest-helpers": "link:bazel-bin/packages/kbn-test-jest-helpers/npm_module_types", "@types/kbn__tooling-log": "link:bazel-bin/packages/kbn-tooling-log/npm_module_types", + "@types/kbn__type-summarizer": "link:bazel-bin/packages/kbn-type-summarizer/npm_module_types", + "@types/kbn__type-summarizer-cli": "link:bazel-bin/packages/kbn-type-summarizer-cli/npm_module_types", + "@types/kbn__type-summarizer-core": "link:bazel-bin/packages/kbn-type-summarizer-core/npm_module_types", "@types/kbn__typed-react-router-config": "link:bazel-bin/packages/kbn-typed-react-router-config/npm_module_types", "@types/kbn__ui-shared-deps-npm": "link:bazel-bin/packages/kbn-ui-shared-deps-npm/npm_module_types", "@types/kbn__ui-shared-deps-src": "link:bazel-bin/packages/kbn-ui-shared-deps-src/npm_module_types", @@ -933,7 +946,7 @@ "babel-plugin-require-context-hook": "^1.0.0", "babel-plugin-styled-components": "^2.0.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", - "backport": "^8.5.2", + "backport": "^8.8.0", "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", @@ -966,7 +979,7 @@ "enzyme-adapter-utils": "^1.14.0", "enzyme-to-json": "^3.6.1", "eslint": "^7.32.0", - "eslint-config-prettier": "^7.2.0", + "eslint-config-prettier": "^8.5.0", "eslint-module-utils": "^2.6.2", "eslint-plugin-ban": "^1.5.2", "eslint-plugin-cypress": "^2.12.1", @@ -1026,7 +1039,7 @@ "json-schema-typed": "^7.0.3", "json5": "^1.0.1", "jsondiffpatch": "0.4.1", - "license-checker": "^16.0.0", + "license-checker": "^25.0.1", "listr": "^0.14.1", "lmdb-store": "^1.6.11", "marge": "^1.0.1", @@ -1056,7 +1069,7 @@ "postcss": "^7.0.32", "postcss-loader": "^3.0.0", "postcss-prefix-selector": "^1.7.2", - "prettier": "^2.6.2", + "prettier": "^2.7.1", "pretty-format": "^27.5.1", "q": "^1.5.1", "raw-loader": "^3.1.0", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 5ea9e412221b2..07d7212cf6a79 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -158,6 +158,8 @@ filegroup( "//packages/kbn-timelion-grammar:build", "//packages/kbn-tinymath:build", "//packages/kbn-tooling-log:build", + "//packages/kbn-type-summarizer-cli:build", + "//packages/kbn-type-summarizer-core:build", "//packages/kbn-type-summarizer:build", "//packages/kbn-typed-react-router-config:build", "//packages/kbn-ui-framework:build", @@ -176,6 +178,7 @@ filegroup( "//packages/shared-ux/page/kibana_no_data:build", "//packages/shared-ux/prompt/no_data_views:build", "//x-pack/packages/ml/agg_utils:build", + "//x-pack/packages/ml/aiops_components:build", "//x-pack/packages/ml/aiops_utils:build", "//x-pack/packages/ml/is_populated_object:build", "//x-pack/packages/ml/string_hash:build", @@ -321,6 +324,8 @@ filegroup( "//packages/kbn-test-jest-helpers:build_types", "//packages/kbn-test:build_types", "//packages/kbn-tooling-log:build_types", + "//packages/kbn-type-summarizer-cli:build_types", + "//packages/kbn-type-summarizer-core:build_types", "//packages/kbn-type-summarizer:build_types", "//packages/kbn-typed-react-router-config:build_types", "//packages/kbn-ui-shared-deps-npm:build_types", @@ -338,6 +343,7 @@ filegroup( "//packages/shared-ux/page/kibana_no_data:build_types", "//packages/shared-ux/prompt/no_data_views:build_types", "//x-pack/packages/ml/agg_utils:build_types", + "//x-pack/packages/ml/aiops_components:build_types", "//x-pack/packages/ml/aiops_utils:build_types", "//x-pack/packages/ml/is_populated_object:build_types", "//x-pack/packages/ml/string_hash:build_types", diff --git a/packages/analytics/client/BUILD.bazel b/packages/analytics/client/BUILD.bazel index 1e7c3c9ade18a..7d2f45d677ce4 100644 --- a/packages/analytics/client/BUILD.bazel +++ b/packages/analytics/client/BUILD.bazel @@ -88,6 +88,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/analytics/client/tsconfig.json b/packages/analytics/client/tsconfig.json index 0778503efa3af..9b4137148c44d 100644 --- a/packages/analytics/client/tsconfig.json +++ b/packages/analytics/client/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/analytics/shippers/elastic_v3/browser/BUILD.bazel b/packages/analytics/shippers/elastic_v3/browser/BUILD.bazel index 63332da454feb..b8b4420213a29 100644 --- a/packages/analytics/shippers/elastic_v3/browser/BUILD.bazel +++ b/packages/analytics/shippers/elastic_v3/browser/BUILD.bazel @@ -87,6 +87,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/analytics/shippers/elastic_v3/browser/tsconfig.json b/packages/analytics/shippers/elastic_v3/browser/tsconfig.json index b81a9a0217634..894f01d3220e9 100644 --- a/packages/analytics/shippers/elastic_v3/browser/tsconfig.json +++ b/packages/analytics/shippers/elastic_v3/browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/analytics/shippers/elastic_v3/common/BUILD.bazel b/packages/analytics/shippers/elastic_v3/common/BUILD.bazel index 5a71eb77a1dea..585c06ffe1e97 100644 --- a/packages/analytics/shippers/elastic_v3/common/BUILD.bazel +++ b/packages/analytics/shippers/elastic_v3/common/BUILD.bazel @@ -83,6 +83,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/analytics/shippers/elastic_v3/common/tsconfig.json b/packages/analytics/shippers/elastic_v3/common/tsconfig.json index b81a9a0217634..894f01d3220e9 100644 --- a/packages/analytics/shippers/elastic_v3/common/tsconfig.json +++ b/packages/analytics/shippers/elastic_v3/common/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/analytics/shippers/elastic_v3/server/BUILD.bazel b/packages/analytics/shippers/elastic_v3/server/BUILD.bazel index c05164d1f2b7e..25ea87f027a10 100644 --- a/packages/analytics/shippers/elastic_v3/server/BUILD.bazel +++ b/packages/analytics/shippers/elastic_v3/server/BUILD.bazel @@ -82,6 +82,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/analytics/shippers/elastic_v3/server/tsconfig.json b/packages/analytics/shippers/elastic_v3/server/tsconfig.json index b81a9a0217634..894f01d3220e9 100644 --- a/packages/analytics/shippers/elastic_v3/server/tsconfig.json +++ b/packages/analytics/shippers/elastic_v3/server/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/analytics/shippers/fullstory/BUILD.bazel b/packages/analytics/shippers/fullstory/BUILD.bazel index c7da842e3cd93..f76117669a8ec 100644 --- a/packages/analytics/shippers/fullstory/BUILD.bazel +++ b/packages/analytics/shippers/fullstory/BUILD.bazel @@ -84,6 +84,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/analytics/shippers/fullstory/tsconfig.json b/packages/analytics/shippers/fullstory/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/analytics/shippers/fullstory/tsconfig.json +++ b/packages/analytics/shippers/fullstory/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel b/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel index 866e08370e490..2540ccabd517c 100644 --- a/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel +++ b/packages/core/analytics/core-analytics-browser-internal/BUILD.bazel @@ -76,6 +76,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/analytics/core-analytics-browser-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel b/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel index e8622b0893a31..e266ca96ee68d 100644 --- a/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel +++ b/packages/core/analytics/core-analytics-browser-mocks/BUILD.bazel @@ -65,6 +65,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json b/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/analytics/core-analytics-browser/BUILD.bazel b/packages/core/analytics/core-analytics-browser/BUILD.bazel index 462de88c73c5d..a4308b6f8028b 100644 --- a/packages/core/analytics/core-analytics-browser/BUILD.bazel +++ b/packages/core/analytics/core-analytics-browser/BUILD.bazel @@ -63,6 +63,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/analytics/core-analytics-browser/tsconfig.json b/packages/core/analytics/core-analytics-browser/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/analytics/core-analytics-browser/tsconfig.json +++ b/packages/core/analytics/core-analytics-browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/analytics/core-analytics-server-internal/BUILD.bazel b/packages/core/analytics/core-analytics-server-internal/BUILD.bazel index f4fc5e7ae3818..b7ba4ed35a5a3 100644 --- a/packages/core/analytics/core-analytics-server-internal/BUILD.bazel +++ b/packages/core/analytics/core-analytics-server-internal/BUILD.bazel @@ -60,6 +60,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/analytics/core-analytics-server-internal/tsconfig.json b/packages/core/analytics/core-analytics-server-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/analytics/core-analytics-server-internal/tsconfig.json +++ b/packages/core/analytics/core-analytics-server-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/analytics/core-analytics-server-mocks/BUILD.bazel b/packages/core/analytics/core-analytics-server-mocks/BUILD.bazel index 21f95820735f4..1fff61749d3d6 100644 --- a/packages/core/analytics/core-analytics-server-mocks/BUILD.bazel +++ b/packages/core/analytics/core-analytics-server-mocks/BUILD.bazel @@ -59,6 +59,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/analytics/core-analytics-server-mocks/tsconfig.json b/packages/core/analytics/core-analytics-server-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/analytics/core-analytics-server-mocks/tsconfig.json +++ b/packages/core/analytics/core-analytics-server-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/analytics/core-analytics-server/BUILD.bazel b/packages/core/analytics/core-analytics-server/BUILD.bazel index e59378e010127..f5211db6c329d 100644 --- a/packages/core/analytics/core-analytics-server/BUILD.bazel +++ b/packages/core/analytics/core-analytics-server/BUILD.bazel @@ -55,6 +55,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/analytics/core-analytics-server/tsconfig.json b/packages/core/analytics/core-analytics-server/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/analytics/core-analytics-server/tsconfig.json +++ b/packages/core/analytics/core-analytics-server/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/base/core-base-browser-internal/BUILD.bazel b/packages/core/base/core-base-browser-internal/BUILD.bazel index 692c1f45ced6f..896b037ee302c 100644 --- a/packages/core/base/core-base-browser-internal/BUILD.bazel +++ b/packages/core/base/core-base-browser-internal/BUILD.bazel @@ -67,6 +67,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/base/core-base-browser-internal/tsconfig.json b/packages/core/base/core-base-browser-internal/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/base/core-base-browser-internal/tsconfig.json +++ b/packages/core/base/core-base-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/base/core-base-browser-mocks/BUILD.bazel b/packages/core/base/core-base-browser-mocks/BUILD.bazel index 7b9a2a3027a2f..c1427aad0fc54 100644 --- a/packages/core/base/core-base-browser-mocks/BUILD.bazel +++ b/packages/core/base/core-base-browser-mocks/BUILD.bazel @@ -63,6 +63,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/base/core-base-browser-mocks/tsconfig.json b/packages/core/base/core-base-browser-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/base/core-base-browser-mocks/tsconfig.json +++ b/packages/core/base/core-base-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/base/core-base-common-internal/BUILD.bazel b/packages/core/base/core-base-common-internal/BUILD.bazel index 9095c8da9f311..717afd7aa9d3e 100644 --- a/packages/core/base/core-base-common-internal/BUILD.bazel +++ b/packages/core/base/core-base-common-internal/BUILD.bazel @@ -64,6 +64,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/base/core-base-common-internal/tsconfig.json b/packages/core/base/core-base-common-internal/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/base/core-base-common-internal/tsconfig.json +++ b/packages/core/base/core-base-common-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/base/core-base-common/BUILD.bazel b/packages/core/base/core-base-common/BUILD.bazel index 118e7dbd8f2be..7282d7ad4af18 100644 --- a/packages/core/base/core-base-common/BUILD.bazel +++ b/packages/core/base/core-base-common/BUILD.bazel @@ -55,6 +55,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/base/core-base-common/tsconfig.json b/packages/core/base/core-base-common/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/base/core-base-common/tsconfig.json +++ b/packages/core/base/core-base-common/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/base/core-base-server-internal/BUILD.bazel b/packages/core/base/core-base-server-internal/BUILD.bazel index 6fb2083bed017..4d4c745bab217 100644 --- a/packages/core/base/core-base-server-internal/BUILD.bazel +++ b/packages/core/base/core-base-server-internal/BUILD.bazel @@ -32,6 +32,7 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/jest", "//packages/kbn-config:npm_module_types", + "//packages/kbn-config-schema:npm_module_types", "//packages/kbn-logging:npm_module_types", "//packages/kbn-utility-types:npm_module_types", "//packages/core/base/core-base-common-internal:npm_module_types", @@ -58,6 +59,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/base/core-base-server-internal/tsconfig.json b/packages/core/base/core-base-server-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/base/core-base-server-internal/tsconfig.json +++ b/packages/core/base/core-base-server-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/base/core-base-server-mocks/BUILD.bazel b/packages/core/base/core-base-server-mocks/BUILD.bazel index 558b2c4175c91..81379c55d3b2a 100644 --- a/packages/core/base/core-base-server-mocks/BUILD.bazel +++ b/packages/core/base/core-base-server-mocks/BUILD.bazel @@ -65,6 +65,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/base/core-base-server-mocks/tsconfig.json b/packages/core/base/core-base-server-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/base/core-base-server-mocks/tsconfig.json +++ b/packages/core/base/core-base-server-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/config/core-config-server-internal/BUILD.bazel b/packages/core/config/core-config-server-internal/BUILD.bazel index e05a2c280a063..a8bd60a58a154 100644 --- a/packages/core/config/core-config-server-internal/BUILD.bazel +++ b/packages/core/config/core-config-server-internal/BUILD.bazel @@ -62,6 +62,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/config/core-config-server-internal/tsconfig.json b/packages/core/config/core-config-server-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/config/core-config-server-internal/tsconfig.json +++ b/packages/core/config/core-config-server-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/doc-links/core-doc-links-browser-internal/BUILD.bazel b/packages/core/doc-links/core-doc-links-browser-internal/BUILD.bazel index 8464966e70b38..3a658785ed2ec 100644 --- a/packages/core/doc-links/core-doc-links-browser-internal/BUILD.bazel +++ b/packages/core/doc-links/core-doc-links-browser-internal/BUILD.bazel @@ -66,6 +66,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json b/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/doc-links/core-doc-links-browser-mocks/BUILD.bazel b/packages/core/doc-links/core-doc-links-browser-mocks/BUILD.bazel index ef2c364509cb3..4d8431f329a79 100644 --- a/packages/core/doc-links/core-doc-links-browser-mocks/BUILD.bazel +++ b/packages/core/doc-links/core-doc-links-browser-mocks/BUILD.bazel @@ -68,6 +68,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json b/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/doc-links/core-doc-links-browser/BUILD.bazel b/packages/core/doc-links/core-doc-links-browser/BUILD.bazel index ba884c039aadf..13c7d71c43a7c 100644 --- a/packages/core/doc-links/core-doc-links-browser/BUILD.bazel +++ b/packages/core/doc-links/core-doc-links-browser/BUILD.bazel @@ -63,6 +63,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/doc-links/core-doc-links-browser/tsconfig.json b/packages/core/doc-links/core-doc-links-browser/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/doc-links/core-doc-links-browser/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/doc-links/core-doc-links-server-internal/BUILD.bazel b/packages/core/doc-links/core-doc-links-server-internal/BUILD.bazel index 4e6c2dba1a0e3..716a97a4c9186 100644 --- a/packages/core/doc-links/core-doc-links-server-internal/BUILD.bazel +++ b/packages/core/doc-links/core-doc-links-server-internal/BUILD.bazel @@ -59,6 +59,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json b/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-server-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/doc-links/core-doc-links-server-mocks/BUILD.bazel b/packages/core/doc-links/core-doc-links-server-mocks/BUILD.bazel index aa65bf59621f7..ee73d97fb51a1 100644 --- a/packages/core/doc-links/core-doc-links-server-mocks/BUILD.bazel +++ b/packages/core/doc-links/core-doc-links-server-mocks/BUILD.bazel @@ -60,6 +60,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json b/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-server-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/doc-links/core-doc-links-server/BUILD.bazel b/packages/core/doc-links/core-doc-links-server/BUILD.bazel index 649a78c0052c8..2323e580348b8 100644 --- a/packages/core/doc-links/core-doc-links-server/BUILD.bazel +++ b/packages/core/doc-links/core-doc-links-server/BUILD.bazel @@ -55,6 +55,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/doc-links/core-doc-links-server/tsconfig.json b/packages/core/doc-links/core-doc-links-server/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/doc-links/core-doc-links-server/tsconfig.json +++ b/packages/core/doc-links/core-doc-links-server/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-internal/BUILD.bazel b/packages/core/fatal-errors/core-fatal-errors-browser-internal/BUILD.bazel index e79c856654b0c..c3dcc0c8f8e63 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-internal/BUILD.bazel +++ b/packages/core/fatal-errors/core-fatal-errors-browser-internal/BUILD.bazel @@ -84,6 +84,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json b/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json +++ b/packages/core/fatal-errors/core-fatal-errors-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/BUILD.bazel b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/BUILD.bazel index 647682e9f5b98..f1522e558615a 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/BUILD.bazel +++ b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/BUILD.bazel @@ -68,6 +68,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json +++ b/packages/core/fatal-errors/core-fatal-errors-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser/BUILD.bazel b/packages/core/fatal-errors/core-fatal-errors-browser/BUILD.bazel index 5cd525836c328..6f5f7413bd23f 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser/BUILD.bazel +++ b/packages/core/fatal-errors/core-fatal-errors-browser/BUILD.bazel @@ -65,6 +65,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json b/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json +++ b/packages/core/fatal-errors/core-fatal-errors-browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/i18n/core-i18n-browser-internal/BUILD.bazel b/packages/core/i18n/core-i18n-browser-internal/BUILD.bazel index edc404266491b..3072f1bed5cdc 100644 --- a/packages/core/i18n/core-i18n-browser-internal/BUILD.bazel +++ b/packages/core/i18n/core-i18n-browser-internal/BUILD.bazel @@ -69,6 +69,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/i18n/core-i18n-browser-internal/tsconfig.json b/packages/core/i18n/core-i18n-browser-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/i18n/core-i18n-browser-internal/tsconfig.json +++ b/packages/core/i18n/core-i18n-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/i18n/core-i18n-browser-mocks/BUILD.bazel b/packages/core/i18n/core-i18n-browser-mocks/BUILD.bazel index 4a3b9ec38e879..a80fdfe8c9637 100644 --- a/packages/core/i18n/core-i18n-browser-mocks/BUILD.bazel +++ b/packages/core/i18n/core-i18n-browser-mocks/BUILD.bazel @@ -68,6 +68,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json b/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json +++ b/packages/core/i18n/core-i18n-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/i18n/core-i18n-browser/BUILD.bazel b/packages/core/i18n/core-i18n-browser/BUILD.bazel index 6c44bae7866b0..744bf51a5525a 100644 --- a/packages/core/i18n/core-i18n-browser/BUILD.bazel +++ b/packages/core/i18n/core-i18n-browser/BUILD.bazel @@ -64,6 +64,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/i18n/core-i18n-browser/tsconfig.json b/packages/core/i18n/core-i18n-browser/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/i18n/core-i18n-browser/tsconfig.json +++ b/packages/core/i18n/core-i18n-browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-internal/BUILD.bazel b/packages/core/injected-metadata/core-injected-metadata-browser-internal/BUILD.bazel index a83771bb84744..ad44974c2eac2 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-internal/BUILD.bazel +++ b/packages/core/injected-metadata/core-injected-metadata-browser-internal/BUILD.bazel @@ -36,6 +36,7 @@ TYPES_DEPS = [ "@npm//@types/jest", "@npm//@types/lodash", "//packages/kbn-std:npm_module_types", + "//packages/kbn-ui-shared-deps-npm:npm_module_types", "//packages/core/base/core-base-common:npm_module_types", "//packages/core/injected-metadata/core-injected-metadata-common-internal:npm_module_types", ] @@ -68,6 +69,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/BUILD.bazel b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/BUILD.bazel index f4788208b932d..c156f4e573e17 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/BUILD.bazel +++ b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/BUILD.bazel @@ -32,8 +32,8 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/jest", + "//packages/kbn-utility-types:npm_module_types", "//packages/core/injected-metadata/core-injected-metadata-browser-internal:npm_module_types", - ] jsts_transpiler( @@ -64,6 +64,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser/BUILD.bazel b/packages/core/injected-metadata/core-injected-metadata-browser/BUILD.bazel index ace4ba52be72b..d664abe391e8d 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser/BUILD.bazel +++ b/packages/core/injected-metadata/core-injected-metadata-browser/BUILD.bazel @@ -83,6 +83,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-common-internal/BUILD.bazel b/packages/core/injected-metadata/core-injected-metadata-common-internal/BUILD.bazel index 49bf853fea8de..59439698e2aa8 100644 --- a/packages/core/injected-metadata/core-injected-metadata-common-internal/BUILD.bazel +++ b/packages/core/injected-metadata/core-injected-metadata-common-internal/BUILD.bazel @@ -67,6 +67,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json b/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json +++ b/packages/core/injected-metadata/core-injected-metadata-common-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/logging/core-logging-server-internal/BUILD.bazel b/packages/core/logging/core-logging-server-internal/BUILD.bazel index feceb02d2a881..ea2e1e1550bdf 100644 --- a/packages/core/logging/core-logging-server-internal/BUILD.bazel +++ b/packages/core/logging/core-logging-server-internal/BUILD.bazel @@ -37,6 +37,7 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/jest", "@npm//@types/lodash", + "@npm//rxjs", "@npm//@types/moment-timezone", "@npm//elastic-apm-node", "//packages/elastic-safer-lodash-set:npm_module_types", @@ -67,6 +68,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/logging/core-logging-server-internal/tsconfig.json b/packages/core/logging/core-logging-server-internal/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/logging/core-logging-server-internal/tsconfig.json +++ b/packages/core/logging/core-logging-server-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/logging/core-logging-server-mocks/BUILD.bazel b/packages/core/logging/core-logging-server-mocks/BUILD.bazel index 66a6251be3b92..b6f811fb15d7f 100644 --- a/packages/core/logging/core-logging-server-mocks/BUILD.bazel +++ b/packages/core/logging/core-logging-server-mocks/BUILD.bazel @@ -60,6 +60,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/logging/core-logging-server-mocks/tsconfig.json b/packages/core/logging/core-logging-server-mocks/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/logging/core-logging-server-mocks/tsconfig.json +++ b/packages/core/logging/core-logging-server-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/logging/core-logging-server/BUILD.bazel b/packages/core/logging/core-logging-server/BUILD.bazel index 0fed3630e6f5a..f982edcb0339b 100644 --- a/packages/core/logging/core-logging-server/BUILD.bazel +++ b/packages/core/logging/core-logging-server/BUILD.bazel @@ -58,6 +58,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/logging/core-logging-server/tsconfig.json b/packages/core/logging/core-logging-server/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/logging/core-logging-server/tsconfig.json +++ b/packages/core/logging/core-logging-server/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/theme/core-theme-browser-internal/BUILD.bazel b/packages/core/theme/core-theme-browser-internal/BUILD.bazel index 4dc740ef5804d..aa057058229c6 100644 --- a/packages/core/theme/core-theme-browser-internal/BUILD.bazel +++ b/packages/core/theme/core-theme-browser-internal/BUILD.bazel @@ -77,6 +77,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/theme/core-theme-browser-internal/tsconfig.json b/packages/core/theme/core-theme-browser-internal/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/theme/core-theme-browser-internal/tsconfig.json +++ b/packages/core/theme/core-theme-browser-internal/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/theme/core-theme-browser-mocks/BUILD.bazel b/packages/core/theme/core-theme-browser-mocks/BUILD.bazel index 7d554da3b93c5..fbca6b9fc7191 100644 --- a/packages/core/theme/core-theme-browser-mocks/BUILD.bazel +++ b/packages/core/theme/core-theme-browser-mocks/BUILD.bazel @@ -34,6 +34,8 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/jest", "@npm//@types/react", + "@npm//rxjs", + "//packages/kbn-utility-types:npm_module_types", "//packages/core/theme/core-theme-browser:npm_module_types", "//packages/core/theme/core-theme-browser-internal:npm_module_types" ] @@ -66,6 +68,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/theme/core-theme-browser-mocks/tsconfig.json b/packages/core/theme/core-theme-browser-mocks/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/packages/core/theme/core-theme-browser-mocks/tsconfig.json +++ b/packages/core/theme/core-theme-browser-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/core/theme/core-theme-browser/BUILD.bazel b/packages/core/theme/core-theme-browser/BUILD.bazel index f567af1277876..0d6e6b2b72a37 100644 --- a/packages/core/theme/core-theme-browser/BUILD.bazel +++ b/packages/core/theme/core-theme-browser/BUILD.bazel @@ -63,6 +63,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/core/theme/core-theme-browser/tsconfig.json b/packages/core/theme/core-theme-browser/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/packages/core/theme/core-theme-browser/tsconfig.json +++ b/packages/core/theme/core-theme-browser/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/elastic-apm-synthtrace/BUILD.bazel b/packages/elastic-apm-synthtrace/BUILD.bazel index 2fe37502a299a..80deba23034ee 100644 --- a/packages/elastic-apm-synthtrace/BUILD.bazel +++ b/packages/elastic-apm-synthtrace/BUILD.bazel @@ -70,6 +70,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/elastic-apm-synthtrace/src/lib/apm/instance.ts b/packages/elastic-apm-synthtrace/src/lib/apm/instance.ts index 9a7664e9518ce..c89fda7f576fb 100644 --- a/packages/elastic-apm-synthtrace/src/lib/apm/instance.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/instance.ts @@ -39,6 +39,11 @@ export class Instance extends Entity { }); } + containerId(containerId: string) { + this.fields['container.id'] = containerId; + return this; + } + podId(podId: string) { this.fields['kubernetes.pod.uid'] = podId; return this; diff --git a/packages/elastic-apm-synthtrace/src/lib/apm/service.ts b/packages/elastic-apm-synthtrace/src/lib/apm/service.ts index d55f60d86e4db..ded149f0b6236 100644 --- a/packages/elastic-apm-synthtrace/src/lib/apm/service.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/service.ts @@ -16,7 +16,6 @@ export class Service extends Entity { ...this.fields, ['service.node.name']: instanceName, 'host.name': instanceName, - 'container.id': instanceName, }); } } diff --git a/packages/elastic-apm-synthtrace/src/scenarios/aws_lambda.ts b/packages/elastic-apm-synthtrace/src/scenarios/aws_lambda.ts new file mode 100644 index 0000000000000..7069da6d43802 --- /dev/null +++ b/packages/elastic-apm-synthtrace/src/scenarios/aws_lambda.ts @@ -0,0 +1,58 @@ +/* + * 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 { apm, timerange } from '..'; +import { ApmFields } from '../lib/apm/apm_fields'; +import { Scenario } from '../scripts/scenario'; +import { getLogger } from '../scripts/utils/get_common_services'; +import { RunOptions } from '../scripts/utils/parse_run_cli_flags'; + +const scenario: Scenario = async (runOptions: RunOptions) => { + const logger = getLogger(runOptions); + + return { + generate: ({ from, to }) => { + const range = timerange(from, to); + const timestamps = range.interval('1s').rate(3); + + const instance = apm.service('lambda-python', 'production', 'python').instance('instance'); + + const traceEventsSetups = [ + { functionName: 'lambda-python-1', coldStart: true }, + { functionName: 'lambda-python-2', coldStart: false }, + ]; + + const traceEvents = ({ functionName, coldStart }: typeof traceEventsSetups[0]) => { + return timestamps.generator((timestamp) => + instance + .transaction('GET /order/{id}') + .defaults({ + 'service.runtime.name': 'AWS_Lambda_python3.8', + 'cloud.provider': 'aws', + 'cloud.service.name': 'lambda', + 'cloud.region': 'us-east-1', + 'faas.id': `arn:aws:lambda:us-west-2:123456789012:function:${functionName}`, + 'faas.coldstart': coldStart, + 'faas.trigger.type': 'other', + }) + .timestamp(timestamp) + .duration(1000) + .success() + ); + }; + + return traceEventsSetups + .map((traceEventsSetup) => + logger.perf('generating_apm_events', () => traceEvents(traceEventsSetup)) + ) + .reduce((p, c) => p.merge(c)); + }, + }; +}; + +export default scenario; diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts index 530856cb956dc..1dedef597e716 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts @@ -17,7 +17,7 @@ describe('simple trace', () => { beforeEach(() => { const javaService = apm.service('opbeans-java', 'production', 'java'); - const javaInstance = javaService.instance('instance-1'); + const javaInstance = javaService.instance('instance-1').containerId('instance-1'); const range = timerange( new Date('2021-01-01T00:00:00.000Z'), diff --git a/packages/elastic-apm-synthtrace/tsconfig.json b/packages/elastic-apm-synthtrace/tsconfig.json index 9d9a6ecc6b2b6..1e3c48841d4ca 100644 --- a/packages/elastic-apm-synthtrace/tsconfig.json +++ b/packages/elastic-apm-synthtrace/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "./src", diff --git a/packages/kbn-ambient-storybook-types/tsconfig.json b/packages/kbn-ambient-storybook-types/tsconfig.json index b816729f8b354..68ffb14bbba9b 100644 --- a/packages/kbn-ambient-storybook-types/tsconfig.json +++ b/packages/kbn-ambient-storybook-types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-ambient-ui-types/tsconfig.json b/packages/kbn-ambient-ui-types/tsconfig.json index a86b5dfec75b9..ad9af24958c59 100644 --- a/packages/kbn-ambient-ui-types/tsconfig.json +++ b/packages/kbn-ambient-ui-types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "stripInternal": false, diff --git a/packages/kbn-axe-config/BUILD.bazel b/packages/kbn-axe-config/BUILD.bazel index d6498ed1546e3..cc293b58c2a4f 100644 --- a/packages/kbn-axe-config/BUILD.bazel +++ b/packages/kbn-axe-config/BUILD.bazel @@ -82,6 +82,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-axe-config/tsconfig.json b/packages/kbn-axe-config/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-axe-config/tsconfig.json +++ b/packages/kbn-axe-config/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-bazel-packages/BUILD.bazel b/packages/kbn-bazel-packages/BUILD.bazel index 024282768d633..1418d61a1ba46 100644 --- a/packages/kbn-bazel-packages/BUILD.bazel +++ b/packages/kbn-bazel-packages/BUILD.bazel @@ -58,6 +58,7 @@ TYPES_DEPS = [ "//packages/kbn-utils:npm_module_types", "//packages/kbn-std:npm_module_types", "//packages/kbn-synthetic-package-map:npm_module_types", + "@npm//@types/node", "@npm//@types/normalize-path", "@npm//globby", "@npm//normalize-path", @@ -84,6 +85,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-bazel-packages/tsconfig.json b/packages/kbn-bazel-packages/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-bazel-packages/tsconfig.json +++ b/packages/kbn-bazel-packages/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-bazel-runner/BUILD.bazel b/packages/kbn-bazel-runner/BUILD.bazel index ace7e0f9c7662..1f7c95fc9d2b4 100644 --- a/packages/kbn-bazel-runner/BUILD.bazel +++ b/packages/kbn-bazel-runner/BUILD.bazel @@ -58,6 +58,7 @@ TYPES_DEPS = [ "@npm//chalk", "@npm//rxjs", "//packages/kbn-dev-utils:npm_module_types", + "//packages/kbn-tooling-log:npm_module_types", ] jsts_transpiler( @@ -81,6 +82,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-bazel-runner/tsconfig.json b/packages/kbn-bazel-runner/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-bazel-runner/tsconfig.json +++ b/packages/kbn-bazel-runner/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-ci-stats-core/BUILD.bazel b/packages/kbn-ci-stats-core/BUILD.bazel index 421ec5e1961d9..0ffe582109949 100644 --- a/packages/kbn-ci-stats-core/BUILD.bazel +++ b/packages/kbn-ci-stats-core/BUILD.bazel @@ -75,6 +75,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-ci-stats-core/tsconfig.json b/packages/kbn-ci-stats-core/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-ci-stats-core/tsconfig.json +++ b/packages/kbn-ci-stats-core/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-ci-stats-performance-metrics/BUILD.bazel b/packages/kbn-ci-stats-performance-metrics/BUILD.bazel index ff475252a3e99..dfa2046e0aaf8 100644 --- a/packages/kbn-ci-stats-performance-metrics/BUILD.bazel +++ b/packages/kbn-ci-stats-performance-metrics/BUILD.bazel @@ -83,6 +83,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-ci-stats-performance-metrics/tsconfig.json b/packages/kbn-ci-stats-performance-metrics/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-ci-stats-performance-metrics/tsconfig.json +++ b/packages/kbn-ci-stats-performance-metrics/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-ci-stats-reporter/BUILD.bazel b/packages/kbn-ci-stats-reporter/BUILD.bazel index 41ff5a697977c..d3fd4780dc2ab 100644 --- a/packages/kbn-ci-stats-reporter/BUILD.bazel +++ b/packages/kbn-ci-stats-reporter/BUILD.bazel @@ -81,6 +81,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-ci-stats-reporter/tsconfig.json b/packages/kbn-ci-stats-reporter/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-ci-stats-reporter/tsconfig.json +++ b/packages/kbn-ci-stats-reporter/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel index e672f98d5b81d..a7205386dbae1 100644 --- a/packages/kbn-cli-dev-mode/BUILD.bazel +++ b/packages/kbn-cli-dev-mode/BUILD.bazel @@ -91,6 +91,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-cli-dev-mode/tsconfig.json b/packages/kbn-cli-dev-mode/tsconfig.json index 4ba84d786cb4b..b0465bfe8d236 100644 --- a/packages/kbn-cli-dev-mode/tsconfig.json +++ b/packages/kbn-cli-dev-mode/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "./src", diff --git a/packages/kbn-coloring/BUILD.bazel b/packages/kbn-coloring/BUILD.bazel index cbbb4d2b7d3e8..a2c3023df2a71 100644 --- a/packages/kbn-coloring/BUILD.bazel +++ b/packages/kbn-coloring/BUILD.bazel @@ -109,6 +109,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.tsx b/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.tsx index 5db80ba80446c..3bca5955182a9 100644 --- a/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.tsx +++ b/packages/kbn-coloring/src/shared_components/coloring/palette_configuration.tsx @@ -110,6 +110,7 @@ export const CustomizablePalette = ({ {showRangeTypeSelector && ( {i18n.translate('coloring.dynamicColoring.rangeType.label', { @@ -131,6 +132,7 @@ export const CustomizablePalette = ({ display="rowCompressed" > )} diff --git a/packages/kbn-coloring/src/shared_components/coloring/palette_picker.tsx b/packages/kbn-coloring/src/shared_components/coloring/palette_picker.tsx index 941a9f20527a8..5cb2466d48421 100644 --- a/packages/kbn-coloring/src/shared_components/coloring/palette_picker.tsx +++ b/packages/kbn-coloring/src/shared_components/coloring/palette_picker.tsx @@ -98,6 +98,7 @@ export function PalettePicker({ } return ( ", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-generate/tsconfig.json b/packages/kbn-generate/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-generate/tsconfig.json +++ b/packages/kbn-generate/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-handlebars/BUILD.bazel b/packages/kbn-handlebars/BUILD.bazel index 362c153fb90af..ecffc24b4015c 100644 --- a/packages/kbn-handlebars/BUILD.bazel +++ b/packages/kbn-handlebars/BUILD.bazel @@ -69,7 +69,6 @@ ts_project( declaration_map = True, emit_declaration_only = True, out_dir = "target_types", - source_map = True, root_dir = "src", tsconfig = ":tsconfig", ) diff --git a/packages/kbn-handlebars/tsconfig.json b/packages/kbn-handlebars/tsconfig.json index 5ae9ac7b0c6f0..17d2768139200 100644 --- a/packages/kbn-handlebars/tsconfig.json +++ b/packages/kbn-handlebars/tsconfig.json @@ -6,8 +6,6 @@ "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", - "sourceMap": true, - "sourceRoot": "../../../../packages/kbn-handlebars/src", "types": [ "jest", "node" diff --git a/packages/kbn-i18n-react/BUILD.bazel b/packages/kbn-i18n-react/BUILD.bazel index 0ba7335390380..6d4eda04ee464 100644 --- a/packages/kbn-i18n-react/BUILD.bazel +++ b/packages/kbn-i18n-react/BUILD.bazel @@ -73,6 +73,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-i18n-react/tsconfig.json b/packages/kbn-i18n-react/tsconfig.json index 5f0c08bace6d3..24e180d7b52a8 100644 --- a/packages/kbn-i18n-react/tsconfig.json +++ b/packages/kbn-i18n-react/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "types": [ diff --git a/packages/kbn-i18n/BUILD.bazel b/packages/kbn-i18n/BUILD.bazel index 07b2c0b4d773a..37129406f8226 100644 --- a/packages/kbn-i18n/BUILD.bazel +++ b/packages/kbn-i18n/BUILD.bazel @@ -74,6 +74,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-i18n/tsconfig.json b/packages/kbn-i18n/tsconfig.json index 0fd24d62281b6..fbd3a7311a288 100644 --- a/packages/kbn-i18n/tsconfig.json +++ b/packages/kbn-i18n/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "types": [ diff --git a/packages/kbn-import-resolver/BUILD.bazel b/packages/kbn-import-resolver/BUILD.bazel index f6ad73e494486..f2a022970cf98 100644 --- a/packages/kbn-import-resolver/BUILD.bazel +++ b/packages/kbn-import-resolver/BUILD.bazel @@ -84,6 +84,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-import-resolver/tsconfig.json b/packages/kbn-import-resolver/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-import-resolver/tsconfig.json +++ b/packages/kbn-import-resolver/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-interpreter/BUILD.bazel b/packages/kbn-interpreter/BUILD.bazel index f1c066d4cbd84..00c9f172934bb 100644 --- a/packages/kbn-interpreter/BUILD.bazel +++ b/packages/kbn-interpreter/BUILD.bazel @@ -80,6 +80,7 @@ ts_project( deps = TYPES_DEPS, allow_js = True, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-interpreter/tsconfig.json b/packages/kbn-interpreter/tsconfig.json index de30741c59a60..3a0bb15ca5d8f 100644 --- a/packages/kbn-interpreter/tsconfig.json +++ b/packages/kbn-interpreter/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", diff --git a/packages/kbn-io-ts-utils/BUILD.bazel b/packages/kbn-io-ts-utils/BUILD.bazel index 15917e75a5285..9ebd9e3cbd4c3 100644 --- a/packages/kbn-io-ts-utils/BUILD.bazel +++ b/packages/kbn-io-ts-utils/BUILD.bazel @@ -71,6 +71,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-io-ts-utils/tsconfig.json b/packages/kbn-io-ts-utils/tsconfig.json index 1998f132ffd24..077021a130506 100644 --- a/packages/kbn-io-ts-utils/tsconfig.json +++ b/packages/kbn-io-ts-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", diff --git a/packages/kbn-jest-serializers/BUILD.bazel b/packages/kbn-jest-serializers/BUILD.bazel index 23f0c76ab17dd..7661fa12b7814 100644 --- a/packages/kbn-jest-serializers/BUILD.bazel +++ b/packages/kbn-jest-serializers/BUILD.bazel @@ -77,6 +77,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-jest-serializers/tsconfig.json b/packages/kbn-jest-serializers/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-jest-serializers/tsconfig.json +++ b/packages/kbn-jest-serializers/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-kibana-json-schema/BUILD.bazel b/packages/kbn-kibana-json-schema/BUILD.bazel index ff863dd7d6d0a..df7e96d012266 100644 --- a/packages/kbn-kibana-json-schema/BUILD.bazel +++ b/packages/kbn-kibana-json-schema/BUILD.bazel @@ -76,6 +76,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-kibana-json-schema/tsconfig.json b/packages/kbn-kibana-json-schema/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-kibana-json-schema/tsconfig.json +++ b/packages/kbn-kibana-json-schema/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-logging-mocks/BUILD.bazel b/packages/kbn-logging-mocks/BUILD.bazel index 90a2046444291..36f904d3ba3a5 100644 --- a/packages/kbn-logging-mocks/BUILD.bazel +++ b/packages/kbn-logging-mocks/BUILD.bazel @@ -56,6 +56,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-logging-mocks/tsconfig.json b/packages/kbn-logging-mocks/tsconfig.json index ee25c507e7f56..9a5d48e115f21 100644 --- a/packages/kbn-logging-mocks/tsconfig.json +++ b/packages/kbn-logging-mocks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-logging/BUILD.bazel b/packages/kbn-logging/BUILD.bazel index ec25b5cd3ae88..9d4358df545aa 100644 --- a/packages/kbn-logging/BUILD.bazel +++ b/packages/kbn-logging/BUILD.bazel @@ -57,6 +57,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-logging/tsconfig.json b/packages/kbn-logging/tsconfig.json index ee25c507e7f56..9a5d48e115f21 100644 --- a/packages/kbn-logging/tsconfig.json +++ b/packages/kbn-logging/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-mapbox-gl/BUILD.bazel b/packages/kbn-mapbox-gl/BUILD.bazel index d4cebd52152f0..23b236f69392e 100644 --- a/packages/kbn-mapbox-gl/BUILD.bazel +++ b/packages/kbn-mapbox-gl/BUILD.bazel @@ -67,6 +67,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-mapbox-gl/tsconfig.json b/packages/kbn-mapbox-gl/tsconfig.json index ee063bd30933e..d88a4ac0bddad 100644 --- a/packages/kbn-mapbox-gl/tsconfig.json +++ b/packages/kbn-mapbox-gl/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-monaco/BUILD.bazel b/packages/kbn-monaco/BUILD.bazel index c615196f5c182..fb6d67780b0d0 100644 --- a/packages/kbn-monaco/BUILD.bazel +++ b/packages/kbn-monaco/BUILD.bazel @@ -95,6 +95,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-monaco/tsconfig.json b/packages/kbn-monaco/tsconfig.json index e55d786c41bc3..ac08760af8ca1 100644 --- a/packages/kbn-monaco/tsconfig.json +++ b/packages/kbn-monaco/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "types": [ diff --git a/packages/kbn-optimizer-webpack-helpers/BUILD.bazel b/packages/kbn-optimizer-webpack-helpers/BUILD.bazel index a27bcf4d31586..73a5a764559b1 100644 --- a/packages/kbn-optimizer-webpack-helpers/BUILD.bazel +++ b/packages/kbn-optimizer-webpack-helpers/BUILD.bazel @@ -74,6 +74,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-optimizer-webpack-helpers/tsconfig.json b/packages/kbn-optimizer-webpack-helpers/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-optimizer-webpack-helpers/tsconfig.json +++ b/packages/kbn-optimizer-webpack-helpers/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-optimizer/BUILD.bazel b/packages/kbn-optimizer/BUILD.bazel index e011a3f9561c4..a514c43b9ce5a 100644 --- a/packages/kbn-optimizer/BUILD.bazel +++ b/packages/kbn-optimizer/BUILD.bazel @@ -71,6 +71,7 @@ TYPES_DEPS = [ "//packages/kbn-ui-shared-deps-npm:npm_module_types", "//packages/kbn-ui-shared-deps-src:npm_module_types", "//packages/kbn-utils:npm_module_types", + "//packages/kbn-tooling-log:npm_module_types", "//packages/kbn-synthetic-package-map:npm_module_types", "@npm//chalk", "@npm//clean-webpack-plugin", @@ -118,6 +119,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-optimizer/tsconfig.json b/packages/kbn-optimizer/tsconfig.json index 72de52e593cf7..ca3f763a67cee 100644 --- a/packages/kbn-optimizer/tsconfig.json +++ b/packages/kbn-optimizer/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "./src", diff --git a/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel b/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel index c20b1bfca088a..0c5e1bab8f848 100644 --- a/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel +++ b/packages/kbn-performance-testing-dataset-extractor/BUILD.bazel @@ -84,6 +84,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-performance-testing-dataset-extractor/tsconfig.json b/packages/kbn-performance-testing-dataset-extractor/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-performance-testing-dataset-extractor/tsconfig.json +++ b/packages/kbn-performance-testing-dataset-extractor/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-plugin-generator/BUILD.bazel b/packages/kbn-plugin-generator/BUILD.bazel index 3e1565e86fe0d..afc9606e5f93f 100644 --- a/packages/kbn-plugin-generator/BUILD.bazel +++ b/packages/kbn-plugin-generator/BUILD.bazel @@ -85,6 +85,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json index 519de9b703140..99c3a6cf17c1a 100644 --- a/packages/kbn-plugin-generator/tsconfig.json +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "target": "ES2019", diff --git a/packages/kbn-plugin-helpers/BUILD.bazel b/packages/kbn-plugin-helpers/BUILD.bazel index cda9dce927e35..a420acb9f91e9 100644 --- a/packages/kbn-plugin-helpers/BUILD.bazel +++ b/packages/kbn-plugin-helpers/BUILD.bazel @@ -78,6 +78,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-plugin-helpers/tsconfig.json b/packages/kbn-plugin-helpers/tsconfig.json index 0bff4cdbb85e1..5193ef1496240 100644 --- a/packages/kbn-plugin-helpers/tsconfig.json +++ b/packages/kbn-plugin-helpers/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "target": "ES2018", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 3c68dc90dda18..3b48937eb1098 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -56511,2663 +56511,2663 @@ module.exports.default = sortPackageJson /***/ }), -/***/ "../../node_modules/spdx-expression-parse/index.js": +/***/ "../../node_modules/strip-ansi/index.js": /***/ (function(module, exports, __webpack_require__) { -var parser = __webpack_require__("../../node_modules/spdx-expression-parse/parser.js").parser +"use strict"; -module.exports = function (argument) { - return parser.parse(argument) -} +const ansiRegex = __webpack_require__("../../node_modules/ansi-regex/index.js"); + +module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/***/ "../../node_modules/spdx-expression-parse/parser.js": +/***/ "../../node_modules/strip-bom/index.js": /***/ (function(module, exports, __webpack_require__) { -/* WEBPACK VAR INJECTION */(function(module) {/* parser generated by jison 0.4.17 */ -/* - Returns a Parser object of the following structure: +"use strict"; - Parser: { - yy: {} - } - Parser.prototype: { - yy: {}, - trace: function(), - symbols_: {associative list: name ==> number}, - terminals_: {associative list: number ==> name}, - productions_: [...], - performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), - table: [...], - defaultActions: {...}, - parseError: function(str, hash), - parse: function(input), +module.exports = string => { + if (typeof string !== 'string') { + throw new TypeError(`Expected a string, got ${typeof string}`); + } - lexer: { - EOF: 1, - parseError: function(str, hash), - setInput: function(input), - input: function(), - unput: function(str), - more: function(), - less: function(n), - pastInput: function(), - upcomingInput: function(), - showPosition: function(), - test_match: function(regex_match_array, rule_index), - next: function(), - lex: function(), - begin: function(condition), - popState: function(), - _currentRules: function(), - topState: function(), - pushState: function(condition), + // Catches EFBBBF (UTF-8 BOM) because the buffer-to-string + // conversion translates it to FEFF (UTF-16 BOM) + if (string.charCodeAt(0) === 0xFEFF) { + return string.slice(1); + } - options: { - ranges: boolean (optional: true ==> token location info will include a .range[] member) - flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) - backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) - }, + return string; +}; - performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), - rules: [...], - conditions: {associative list: name ==> set}, - } - } +/***/ }), - token location info (@$, _$, etc.): { - first_line: n, - last_line: n, - first_column: n, - last_column: n, - range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) +/***/ "../../node_modules/strip-final-newline/index.js": +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = input => { + const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); + const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); + + if (input[input.length - 1] === LF) { + input = input.slice(0, input.length - 1); + } + + if (input[input.length - 1] === CR) { + input = input.slice(0, input.length - 1); + } + + return input; +}; + + +/***/ }), + +/***/ "../../node_modules/strong-log-transformer/index.js": +/***/ (function(module, exports, __webpack_require__) { + +// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Node module: strong-log-transformer +// This file is licensed under the Apache License 2.0. +// License text available at https://opensource.org/licenses/Apache-2.0 + +module.exports = __webpack_require__("../../node_modules/strong-log-transformer/lib/logger.js"); +module.exports.cli = __webpack_require__("../../node_modules/strong-log-transformer/lib/cli.js"); + + +/***/ }), + +/***/ "../../node_modules/strong-log-transformer/lib/cli.js": +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Node module: strong-log-transformer +// This file is licensed under the Apache License 2.0. +// License text available at https://opensource.org/licenses/Apache-2.0 + + + +var minimist = __webpack_require__("../../node_modules/minimist/index.js"); +var path = __webpack_require__("path"); + +var Logger = __webpack_require__("../../node_modules/strong-log-transformer/lib/logger.js"); +var pkg = __webpack_require__("../../node_modules/strong-log-transformer/package.json"); + +module.exports = cli; + +function cli(args) { + var opts = minimist(args.slice(2)); + var $0 = path.basename(args[1]); + var p = console.log.bind(console); + if (opts.v || opts.version) { + version($0, p); + } else if (opts.h || opts.help) { + usage($0, p); + } else if (args.length < 3) { + process.stdin.pipe(Logger()).pipe(process.stdout); + } else { + process.stdin.pipe(Logger(opts)).pipe(process.stdout); } +} +function version($0, p) { + p('%s v%s', pkg.name, pkg.version); +} - the parseError function receives a 'hash' object with these members for lexer and parser errors: { - text: (matched text) - token: (the produced terminal token, if any) - line: (yylineno) +function usage($0, p) { + var PADDING = ' '; + var opt, def; + p('Usage: %s [options]', $0); + p(''); + p('%s', pkg.description); + p(''); + p('OPTIONS:'); + for (opt in Logger.DEFAULTS) { + def = Logger.DEFAULTS[opt]; + if (typeof def === 'boolean') + boolOpt(opt, Logger.DEFAULTS[opt]); + else + stdOpt(opt, Logger.DEFAULTS[opt]); } - while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { - loc: (yylloc) - expected: (string describing the set of expected tokens) - recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + p(''); + + function boolOpt(name, def) { + name = name + PADDING.slice(0, 20-name.length); + p(' --%s default: %s', name, def); } -*/ -var spdxparse = (function(){ -var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,6],$V2=[1,7],$V3=[1,4],$V4=[1,9],$V5=[1,10],$V6=[5,14,15,17],$V7=[5,12,14,15,17]; -var parser = {trace: function trace() { }, -yy: {}, -symbols_: {"error":2,"start":3,"expression":4,"EOS":5,"simpleExpression":6,"LICENSE":7,"PLUS":8,"LICENSEREF":9,"DOCUMENTREF":10,"COLON":11,"WITH":12,"EXCEPTION":13,"AND":14,"OR":15,"OPEN":16,"CLOSE":17,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOS",7:"LICENSE",8:"PLUS",9:"LICENSEREF",10:"DOCUMENTREF",11:"COLON",12:"WITH",13:"EXCEPTION",14:"AND",15:"OR",16:"OPEN",17:"CLOSE"}, -productions_: [0,[3,2],[6,1],[6,2],[6,1],[6,3],[4,1],[4,3],[4,3],[4,3],[4,3]], -performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { -/* this == yyval */ -var $0 = $$.length - 1; -switch (yystate) { -case 1: -return this.$ = $$[$0-1] -break; -case 2: case 4: case 5: -this.$ = {license: yytext} -break; -case 3: -this.$ = {license: $$[$0-1], plus: true} -break; -case 6: -this.$ = $$[$0] -break; -case 7: -this.$ = {exception: $$[$0]} -this.$.license = $$[$0-2].license -if ($$[$0-2].hasOwnProperty('plus')) { - this.$.plus = $$[$0-2].plus -} -break; -case 8: -this.$ = {conjunction: 'and', left: $$[$0-2], right: $$[$0]} -break; -case 9: -this.$ = {conjunction: 'or', left: $$[$0-2], right: $$[$0]} -break; -case 10: -this.$ = $$[$0-1] -break; + function stdOpt(name, def) { + var value = name.toUpperCase() + + PADDING.slice(0, 19 - name.length*2); + p(' --%s %s default: %j', name, value, def); + } } -}, -table: [{3:1,4:2,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{1:[3]},{5:[1,8],14:$V4,15:$V5},o($V6,[2,6],{12:[1,11]}),{4:12,6:3,7:$V0,9:$V1,10:$V2,16:$V3},o($V7,[2,2],{8:[1,13]}),o($V7,[2,4]),{11:[1,14]},{1:[2,1]},{4:15,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{4:16,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{13:[1,17]},{14:$V4,15:$V5,17:[1,18]},o($V7,[2,3]),{9:[1,19]},o($V6,[2,8]),o([5,15,17],[2,9],{14:$V4}),o($V6,[2,7]),o($V6,[2,10]),o($V7,[2,5])], -defaultActions: {8:[2,1]}, -parseError: function parseError(str, hash) { - if (hash.recoverable) { - this.trace(str); - } else { - function _parseError (msg, hash) { - this.message = msg; - this.hash = hash; - } - _parseError.prototype = Error; - throw new _parseError(str, hash); - } -}, -parse: function parse(input) { - var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; - var args = lstack.slice.call(arguments, 1); - var lexer = Object.create(this.lexer); - var sharedState = { yy: {} }; - for (var k in this.yy) { - if (Object.prototype.hasOwnProperty.call(this.yy, k)) { - sharedState.yy[k] = this.yy[k]; - } - } - lexer.setInput(input, sharedState.yy); - sharedState.yy.lexer = lexer; - sharedState.yy.parser = this; - if (typeof lexer.yylloc == 'undefined') { - lexer.yylloc = {}; - } - var yyloc = lexer.yylloc; - lstack.push(yyloc); - var ranges = lexer.options && lexer.options.ranges; - if (typeof sharedState.yy.parseError === 'function') { - this.parseError = sharedState.yy.parseError; - } else { - this.parseError = Object.getPrototypeOf(this).parseError; - } - function popStack(n) { - stack.length = stack.length - 2 * n; - vstack.length = vstack.length - n; - lstack.length = lstack.length - n; - } - _token_stack: - var lex = function () { - var token; - token = lexer.lex() || EOF; - if (typeof token !== 'number') { - token = self.symbols_[token] || token; - } - return token; - }; - var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; - while (true) { - state = stack[stack.length - 1]; - if (this.defaultActions[state]) { - action = this.defaultActions[state]; - } else { - if (symbol === null || typeof symbol == 'undefined') { - symbol = lex(); - } - action = table[state] && table[state][symbol]; - } - if (typeof action === 'undefined' || !action.length || !action[0]) { - var errStr = ''; - expected = []; - for (p in table[state]) { - if (this.terminals_[p] && p > TERROR) { - expected.push('\'' + this.terminals_[p] + '\''); - } - } - if (lexer.showPosition) { - errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; - } else { - errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); - } - this.parseError(errStr, { - text: lexer.match, - token: this.terminals_[symbol] || symbol, - line: lexer.yylineno, - loc: yyloc, - expected: expected - }); - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(lexer.yytext); - lstack.push(lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = lexer.yyleng; - yytext = lexer.yytext; - yylineno = lexer.yylineno; - yyloc = lexer.yylloc; - if (recovering > 0) { - recovering--; - } - } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = { - first_line: lstack[lstack.length - (len || 1)].first_line, - last_line: lstack[lstack.length - 1].last_line, - first_column: lstack[lstack.length - (len || 1)].first_column, - last_column: lstack[lstack.length - 1].last_column - }; - if (ranges) { - yyval._$.range = [ - lstack[lstack.length - (len || 1)].range[0], - lstack[lstack.length - 1].range[1] - ]; - } - r = this.performAction.apply(yyval, [ - yytext, - yyleng, - yylineno, - sharedState.yy, - action[1], - vstack, - lstack - ].concat(args)); - if (typeof r !== 'undefined') { - return r; - } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); - } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; - } - } - return true; -}}; -/* generated by jison-lex 0.3.4 */ -var lexer = (function(){ -var lexer = ({ -EOF:1, +/***/ }), -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, +/***/ "../../node_modules/strong-log-transformer/lib/logger.js": +/***/ (function(module, exports, __webpack_require__) { -// resets the lexer, sets new input -setInput:function (input, yy) { - this.yy = yy || this.yy || {}; - this._input = input; - this._more = this._backtrack = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = { - first_line: 1, - first_column: 0, - last_line: 1, - last_column: 0 - }; - if (this.options.ranges) { - this.yylloc.range = [0,0]; - } - this.offset = 0; - return this; - }, +"use strict"; +// Copyright IBM Corp. 2014,2018. All Rights Reserved. +// Node module: strong-log-transformer +// This file is licensed under the Apache License 2.0. +// License text available at https://opensource.org/licenses/Apache-2.0 -// consumes and returns one char from the input -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; - } - if (this.options.ranges) { - this.yylloc.range[1]++; - } - this._input = this._input.slice(1); - return ch; - }, -// unshifts one char (or a string) into the input -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); +var stream = __webpack_require__("stream"); +var util = __webpack_require__("util"); +var fs = __webpack_require__("fs"); - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length - len); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length - 1); - this.matched = this.matched.substr(0, this.matched.length - 1); +var through = __webpack_require__("../../node_modules/through/index.js"); +var duplexer = __webpack_require__("../../node_modules/duplexer/index.js"); +var StringDecoder = __webpack_require__("string_decoder").StringDecoder; - if (lines.length - 1) { - this.yylineno -= lines.length - 1; - } - var r = this.yylloc.range; +module.exports = Logger; - this.yylloc = { - first_line: this.yylloc.first_line, - last_line: this.yylineno + 1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) - + oldLines[oldLines.length - lines.length].length - lines[0].length : - this.yylloc.first_column - len - }; +Logger.DEFAULTS = { + format: 'text', + tag: '', + mergeMultiline: false, + timeStamp: false, +}; - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; - } - this.yyleng = this.yytext.length; - return this; - }, +var formatters = { + text: textFormatter, + json: jsonFormatter, +} -// When called from action, caches matched text and appends it on next action -more:function () { - this._more = true; - return this; - }, +function Logger(options) { + var defaults = JSON.parse(JSON.stringify(Logger.DEFAULTS)); + options = util._extend(defaults, options || {}); + var catcher = deLiner(); + var emitter = catcher; + var transforms = [ + objectifier(), + ]; -// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. -reject:function () { - if (this.options.backtrack_lexer) { - this._backtrack = true; - } else { - return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { - text: "", - token: null, - line: this.yylineno - }); + if (options.tag) { + transforms.push(staticTagger(options.tag)); + } - } - return this; - }, + if (options.mergeMultiline) { + transforms.push(lineMerger()); + } -// retain first n characters of the match -less:function (n) { - this.unput(this.match.slice(n)); - }, - -// displays already matched input, i.e. for error messages -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, + // TODO + // if (options.pidStamp) { + // transforms.push(pidStamper(options.pid)); + // } -// displays upcoming input, i.e. for error messages -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); - } - return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); - }, + // TODO + // if (options.workerStamp) { + // transforms.push(workerStamper(options.worker)); + // } -// displays the character position where the lexing error occurred, i.e. for error messages -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c + "^"; - }, + transforms.push(formatters[options.format](options)); -// test the lexed token: return FALSE when not a match, otherwise return token -test_match:function (match, indexed_rule) { - var token, - lines, - backup; + // restore line endings that were removed by line splitting + transforms.push(reLiner()); - if (this.options.backtrack_lexer) { - // save context - backup = { - yylineno: this.yylineno, - yylloc: { - first_line: this.yylloc.first_line, - last_line: this.last_line, - first_column: this.yylloc.first_column, - last_column: this.yylloc.last_column - }, - yytext: this.yytext, - match: this.match, - matches: this.matches, - matched: this.matched, - yyleng: this.yyleng, - offset: this.offset, - _more: this._more, - _input: this._input, - yy: this.yy, - conditionStack: this.conditionStack.slice(0), - done: this.done - }; - if (this.options.ranges) { - backup.yylloc.range = this.yylloc.range.slice(0); - } - } + for (var t in transforms) { + emitter = emitter.pipe(transforms[t]); + } - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno += lines.length; - } - this.yylloc = { - first_line: this.yylloc.last_line, - last_line: this.yylineno + 1, - first_column: this.yylloc.last_column, - last_column: lines ? - lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : - this.yylloc.last_column + match[0].length - }; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._backtrack = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); - if (this.done && this._input) { - this.done = false; - } - if (token) { - return token; - } else if (this._backtrack) { - // recover context - for (var k in backup) { - this[k] = backup[k]; - } - return false; // rule action called reject() implying the next rule should be tested instead. - } - return false; - }, + return duplexer(catcher, emitter); +} -// return next match in input -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) { - this.done = true; - } +function deLiner() { + var decoder = new StringDecoder('utf8'); + var last = ''; - var token, - match, - tempMatch, - index; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i = 0; i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (this.options.backtrack_lexer) { - token = this.test_match(tempMatch, rules[i]); - if (token !== false) { - return token; - } else if (this._backtrack) { - match = false; - continue; // rule action called reject() implying a rule MISmatch. - } else { - // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) - return false; - } - } else if (!this.options.flex) { - break; - } - } - } - if (match) { - token = this.test_match(match, rules[index]); - if (token !== false) { - return token; - } - // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) - return false; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { - text: "", - token: null, - line: this.yylineno - }); + return new stream.Transform({ + transform(chunk, _enc, callback) { + last += decoder.write(chunk); + var list = last.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g); + last = list.pop(); + for (var i = 0; i < list.length; i++) { + // swallow empty lines + if (list[i]) { + this.push(list[i]); } + } + callback(); }, - -// return next match that has a token -lex:function lex() { - var r = this.next(); - if (r) { - return r; - } else { - return this.lex(); - } + flush(callback) { + // incomplete UTF8 sequences become UTF8 replacement characters + last += decoder.end(); + if (last) { + this.push(last); + } + callback(); }, + }); +} -// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) -begin:function begin(condition) { - this.conditionStack.push(condition); - }, +function reLiner() { + return through(appendNewline); -// pop the previously active lexer condition state off the condition stack -popState:function popState() { - var n = this.conditionStack.length - 1; - if (n > 0) { - return this.conditionStack.pop(); - } else { - return this.conditionStack[0]; - } - }, + function appendNewline(line) { + this.emit('data', line + '\n'); + } +} -// produce the lexer rule set which is active for the currently active lexer condition state -_currentRules:function _currentRules() { - if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { - return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; - } else { - return this.conditions["INITIAL"].rules; - } - }, +function objectifier() { + return through(objectify, null, {autoDestroy: false}); -// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available -topState:function topState(n) { - n = this.conditionStack.length - 1 - Math.abs(n || 0); - if (n >= 0) { - return this.conditionStack[n]; - } else { - return "INITIAL"; - } - }, + function objectify(line) { + this.emit('data', { + msg: line, + time: Date.now(), + }); + } +} -// alias for begin(condition) -pushState:function pushState(condition) { - this.begin(condition); - }, +function staticTagger(tag) { + return through(tagger); -// return the number of states currently on the stack -stateStackSize:function stateStackSize() { - return this.conditionStack.length; - }, -options: {}, -performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { -var YYSTATE=YY_START; -switch($avoiding_name_collisions) { -case 0:return 5 -break; -case 1:/* skip whitespace */ -break; -case 2:return 8 -break; -case 3:return 16 -break; -case 4:return 17 -break; -case 5:return 11 -break; -case 6:return 10 -break; -case 7:return 9 -break; -case 8:return 14 -break; -case 9:return 15 -break; -case 10:return 12 -break; -case 11:return 7 -break; -case 12:return 7 -break; -case 13:return 7 -break; -case 14:return 7 -break; -case 15:return 7 -break; -case 16:return 7 -break; -case 17:return 7 -break; -case 18:return 7 -break; -case 19:return 7 -break; -case 20:return 7 -break; -case 21:return 7 -break; -case 22:return 7 -break; -case 23:return 7 -break; -case 24:return 13 -break; -case 25:return 13 -break; -case 26:return 13 -break; -case 27:return 13 -break; -case 28:return 13 -break; -case 29:return 13 -break; -case 30:return 13 -break; -case 31:return 13 -break; -case 32:return 7 -break; -case 33:return 13 -break; -case 34:return 7 -break; -case 35:return 13 -break; -case 36:return 7 -break; -case 37:return 13 -break; -case 38:return 13 -break; -case 39:return 7 -break; -case 40:return 13 -break; -case 41:return 13 -break; -case 42:return 13 -break; -case 43:return 13 -break; -case 44:return 13 -break; -case 45:return 7 -break; -case 46:return 13 -break; -case 47:return 7 -break; -case 48:return 7 -break; -case 49:return 7 -break; -case 50:return 7 -break; -case 51:return 7 -break; -case 52:return 7 -break; -case 53:return 7 -break; -case 54:return 7 -break; -case 55:return 7 -break; -case 56:return 7 -break; -case 57:return 7 -break; -case 58:return 7 -break; -case 59:return 7 -break; -case 60:return 7 -break; -case 61:return 7 -break; -case 62:return 7 -break; -case 63:return 13 -break; -case 64:return 7 -break; -case 65:return 7 -break; -case 66:return 13 -break; -case 67:return 7 -break; -case 68:return 7 -break; -case 69:return 7 -break; -case 70:return 7 -break; -case 71:return 7 -break; -case 72:return 7 -break; -case 73:return 13 -break; -case 74:return 7 -break; -case 75:return 13 -break; -case 76:return 7 -break; -case 77:return 7 -break; -case 78:return 7 -break; -case 79:return 7 -break; -case 80:return 7 -break; -case 81:return 7 -break; -case 82:return 7 -break; -case 83:return 7 -break; -case 84:return 7 -break; -case 85:return 7 -break; -case 86:return 7 -break; -case 87:return 7 -break; -case 88:return 7 -break; -case 89:return 7 -break; -case 90:return 7 -break; -case 91:return 7 -break; -case 92:return 7 -break; -case 93:return 7 -break; -case 94:return 7 -break; -case 95:return 7 -break; -case 96:return 7 -break; -case 97:return 7 -break; -case 98:return 7 -break; -case 99:return 7 -break; -case 100:return 7 -break; -case 101:return 7 -break; -case 102:return 7 -break; -case 103:return 7 -break; -case 104:return 7 -break; -case 105:return 7 -break; -case 106:return 7 -break; -case 107:return 7 -break; -case 108:return 7 -break; -case 109:return 7 -break; -case 110:return 7 -break; -case 111:return 7 -break; -case 112:return 7 -break; -case 113:return 7 -break; -case 114:return 7 -break; -case 115:return 7 -break; -case 116:return 7 -break; -case 117:return 7 -break; -case 118:return 7 -break; -case 119:return 7 -break; -case 120:return 7 -break; -case 121:return 7 -break; -case 122:return 7 -break; -case 123:return 7 -break; -case 124:return 7 -break; -case 125:return 7 -break; -case 126:return 7 -break; -case 127:return 7 -break; -case 128:return 7 -break; -case 129:return 7 -break; -case 130:return 7 -break; -case 131:return 7 -break; -case 132:return 7 -break; -case 133:return 7 -break; -case 134:return 7 -break; -case 135:return 7 -break; -case 136:return 7 -break; -case 137:return 7 -break; -case 138:return 7 -break; -case 139:return 7 -break; -case 140:return 7 -break; -case 141:return 7 -break; -case 142:return 7 -break; -case 143:return 7 -break; -case 144:return 7 -break; -case 145:return 7 -break; -case 146:return 7 -break; -case 147:return 7 -break; -case 148:return 7 -break; -case 149:return 7 -break; -case 150:return 7 -break; -case 151:return 7 -break; -case 152:return 7 -break; -case 153:return 7 -break; -case 154:return 7 -break; -case 155:return 7 -break; -case 156:return 7 -break; -case 157:return 7 -break; -case 158:return 7 -break; -case 159:return 7 -break; -case 160:return 7 -break; -case 161:return 7 -break; -case 162:return 7 -break; -case 163:return 7 -break; -case 164:return 7 -break; -case 165:return 7 -break; -case 166:return 7 -break; -case 167:return 7 -break; -case 168:return 7 -break; -case 169:return 7 -break; -case 170:return 7 -break; -case 171:return 7 -break; -case 172:return 7 -break; -case 173:return 7 -break; -case 174:return 7 -break; -case 175:return 7 -break; -case 176:return 7 -break; -case 177:return 7 -break; -case 178:return 7 -break; -case 179:return 7 -break; -case 180:return 7 -break; -case 181:return 7 -break; -case 182:return 7 -break; -case 183:return 7 -break; -case 184:return 7 -break; -case 185:return 7 -break; -case 186:return 7 -break; -case 187:return 7 -break; -case 188:return 7 -break; -case 189:return 7 -break; -case 190:return 7 -break; -case 191:return 7 -break; -case 192:return 7 -break; -case 193:return 7 -break; -case 194:return 7 -break; -case 195:return 7 -break; -case 196:return 7 -break; -case 197:return 7 -break; -case 198:return 7 -break; -case 199:return 7 -break; -case 200:return 7 -break; -case 201:return 7 -break; -case 202:return 7 -break; -case 203:return 7 -break; -case 204:return 7 -break; -case 205:return 7 -break; -case 206:return 7 -break; -case 207:return 7 -break; -case 208:return 7 -break; -case 209:return 7 -break; -case 210:return 7 -break; -case 211:return 7 -break; -case 212:return 7 -break; -case 213:return 7 -break; -case 214:return 7 -break; -case 215:return 7 -break; -case 216:return 7 -break; -case 217:return 7 -break; -case 218:return 7 -break; -case 219:return 7 -break; -case 220:return 7 -break; -case 221:return 7 -break; -case 222:return 7 -break; -case 223:return 7 -break; -case 224:return 7 -break; -case 225:return 7 -break; -case 226:return 7 -break; -case 227:return 7 -break; -case 228:return 7 -break; -case 229:return 7 -break; -case 230:return 7 -break; -case 231:return 7 -break; -case 232:return 7 -break; -case 233:return 7 -break; -case 234:return 7 -break; -case 235:return 7 -break; -case 236:return 7 -break; -case 237:return 7 -break; -case 238:return 7 -break; -case 239:return 7 -break; -case 240:return 7 -break; -case 241:return 7 -break; -case 242:return 7 -break; -case 243:return 7 -break; -case 244:return 7 -break; -case 245:return 7 -break; -case 246:return 7 -break; -case 247:return 7 -break; -case 248:return 7 -break; -case 249:return 7 -break; -case 250:return 7 -break; -case 251:return 7 -break; -case 252:return 7 -break; -case 253:return 7 -break; -case 254:return 7 -break; -case 255:return 7 -break; -case 256:return 7 -break; -case 257:return 7 -break; -case 258:return 7 -break; -case 259:return 7 -break; -case 260:return 7 -break; -case 261:return 7 -break; -case 262:return 7 -break; -case 263:return 7 -break; -case 264:return 7 -break; -case 265:return 7 -break; -case 266:return 7 -break; -case 267:return 7 -break; -case 268:return 7 -break; -case 269:return 7 -break; -case 270:return 7 -break; -case 271:return 7 -break; -case 272:return 7 -break; -case 273:return 7 -break; -case 274:return 7 -break; -case 275:return 7 -break; -case 276:return 7 -break; -case 277:return 7 -break; -case 278:return 7 -break; -case 279:return 7 -break; -case 280:return 7 -break; -case 281:return 7 -break; -case 282:return 7 -break; -case 283:return 7 -break; -case 284:return 7 -break; -case 285:return 7 -break; -case 286:return 7 -break; -case 287:return 7 -break; -case 288:return 7 -break; -case 289:return 7 -break; -case 290:return 7 -break; -case 291:return 7 -break; -case 292:return 7 -break; -case 293:return 7 -break; -case 294:return 7 -break; -case 295:return 7 -break; -case 296:return 7 -break; -case 297:return 7 -break; -case 298:return 7 -break; -case 299:return 7 -break; -case 300:return 7 -break; -case 301:return 7 -break; -case 302:return 7 -break; -case 303:return 7 -break; -case 304:return 7 -break; -case 305:return 7 -break; -case 306:return 7 -break; -case 307:return 7 -break; -case 308:return 7 -break; -case 309:return 7 -break; -case 310:return 7 -break; -case 311:return 7 -break; -case 312:return 7 -break; -case 313:return 7 -break; -case 314:return 7 -break; -case 315:return 7 -break; -case 316:return 7 -break; -case 317:return 7 -break; -case 318:return 7 -break; -case 319:return 7 -break; -case 320:return 7 -break; -case 321:return 7 -break; -case 322:return 7 -break; -case 323:return 7 -break; -case 324:return 7 -break; -case 325:return 7 -break; -case 326:return 7 -break; -case 327:return 7 -break; -case 328:return 7 -break; -case 329:return 7 -break; -case 330:return 7 -break; -case 331:return 7 -break; -case 332:return 7 -break; -case 333:return 7 -break; -case 334:return 7 -break; -case 335:return 7 -break; -case 336:return 7 -break; -case 337:return 7 -break; -case 338:return 7 -break; -case 339:return 7 -break; -case 340:return 7 -break; -case 341:return 7 -break; -case 342:return 7 -break; -case 343:return 7 -break; -case 344:return 7 -break; -case 345:return 7 -break; -case 346:return 7 -break; -case 347:return 7 -break; -case 348:return 7 -break; -case 349:return 7 -break; -case 350:return 7 -break; -case 351:return 7 -break; -case 352:return 7 -break; -case 353:return 7 -break; -case 354:return 7 -break; -case 355:return 7 -break; -case 356:return 7 -break; -case 357:return 7 -break; -case 358:return 7 -break; -case 359:return 7 -break; -case 360:return 7 -break; -case 361:return 7 -break; -case 362:return 7 -break; -case 363:return 7 -break; -case 364:return 7 -break; + function tagger(logEvent) { + logEvent.tag = tag; + this.emit('data', logEvent); + } } -}, -rules: [/^(?:$)/,/^(?:\s+)/,/^(?:\+)/,/^(?:\()/,/^(?:\))/,/^(?::)/,/^(?:DocumentRef-([0-9A-Za-z-+.]+))/,/^(?:LicenseRef-([0-9A-Za-z-+.]+))/,/^(?:AND)/,/^(?:OR)/,/^(?:WITH)/,/^(?:BSD-3-Clause-No-Nuclear-License-2014)/,/^(?:BSD-3-Clause-No-Nuclear-Warranty)/,/^(?:GPL-2\.0-with-classpath-exception)/,/^(?:GPL-3\.0-with-autoconf-exception)/,/^(?:GPL-2\.0-with-autoconf-exception)/,/^(?:BSD-3-Clause-No-Nuclear-License)/,/^(?:MPL-2\.0-no-copyleft-exception)/,/^(?:GPL-2\.0-with-bison-exception)/,/^(?:GPL-2\.0-with-font-exception)/,/^(?:GPL-2\.0-with-GCC-exception)/,/^(?:CNRI-Python-GPL-Compatible)/,/^(?:GPL-3\.0-with-GCC-exception)/,/^(?:BSD-3-Clause-Attribution)/,/^(?:Classpath-exception-2\.0)/,/^(?:WxWindows-exception-3\.1)/,/^(?:freertos-exception-2\.0)/,/^(?:Autoconf-exception-3\.0)/,/^(?:i2p-gpl-java-exception)/,/^(?:gnu-javamail-exception)/,/^(?:Nokia-Qt-exception-1\.1)/,/^(?:Autoconf-exception-2\.0)/,/^(?:BSD-2-Clause-FreeBSD)/,/^(?:u-boot-exception-2\.0)/,/^(?:zlib-acknowledgement)/,/^(?:Bison-exception-2\.2)/,/^(?:BSD-2-Clause-NetBSD)/,/^(?:CLISP-exception-2\.0)/,/^(?:eCos-exception-2\.0)/,/^(?:BSD-3-Clause-Clear)/,/^(?:Font-exception-2\.0)/,/^(?:FLTK-exception-2\.0)/,/^(?:GCC-exception-2\.0)/,/^(?:Qwt-exception-1\.0)/,/^(?:Libtool-exception)/,/^(?:BSD-3-Clause-LBNL)/,/^(?:GCC-exception-3\.1)/,/^(?:Artistic-1\.0-Perl)/,/^(?:Artistic-1\.0-cl8)/,/^(?:CC-BY-NC-SA-2\.5)/,/^(?:MIT-advertising)/,/^(?:BSD-Source-Code)/,/^(?:CC-BY-NC-SA-4\.0)/,/^(?:LiLiQ-Rplus-1\.1)/,/^(?:CC-BY-NC-SA-3\.0)/,/^(?:BSD-4-Clause-UC)/,/^(?:CC-BY-NC-SA-2\.0)/,/^(?:CC-BY-NC-SA-1\.0)/,/^(?:CC-BY-NC-ND-4\.0)/,/^(?:CC-BY-NC-ND-3\.0)/,/^(?:CC-BY-NC-ND-2\.5)/,/^(?:CC-BY-NC-ND-2\.0)/,/^(?:CC-BY-NC-ND-1\.0)/,/^(?:LZMA-exception)/,/^(?:BitTorrent-1\.1)/,/^(?:CrystalStacker)/,/^(?:FLTK-exception)/,/^(?:SugarCRM-1\.1\.3)/,/^(?:BSD-Protection)/,/^(?:BitTorrent-1\.0)/,/^(?:HaskellReport)/,/^(?:Interbase-1\.0)/,/^(?:StandardML-NJ)/,/^(?:mif-exception)/,/^(?:Frameworx-1\.0)/,/^(?:389-exception)/,/^(?:CC-BY-NC-2\.0)/,/^(?:CC-BY-NC-2\.5)/,/^(?:CC-BY-NC-3\.0)/,/^(?:CC-BY-NC-4\.0)/,/^(?:W3C-19980720)/,/^(?:CC-BY-SA-1\.0)/,/^(?:CC-BY-SA-2\.0)/,/^(?:CC-BY-SA-2\.5)/,/^(?:CC-BY-ND-2\.0)/,/^(?:CC-BY-SA-4\.0)/,/^(?:CC-BY-SA-3\.0)/,/^(?:Artistic-1\.0)/,/^(?:Artistic-2\.0)/,/^(?:CC-BY-ND-2\.5)/,/^(?:CC-BY-ND-3\.0)/,/^(?:CC-BY-ND-4\.0)/,/^(?:CC-BY-ND-1\.0)/,/^(?:BSD-4-Clause)/,/^(?:BSD-3-Clause)/,/^(?:BSD-2-Clause)/,/^(?:CC-BY-NC-1\.0)/,/^(?:bzip2-1\.0\.6)/,/^(?:Unicode-TOU)/,/^(?:CNRI-Jython)/,/^(?:ImageMagick)/,/^(?:Adobe-Glyph)/,/^(?:CUA-OPL-1\.0)/,/^(?:OLDAP-2\.2\.2)/,/^(?:LiLiQ-R-1\.1)/,/^(?:bzip2-1\.0\.5)/,/^(?:LiLiQ-P-1\.1)/,/^(?:OLDAP-2\.0\.1)/,/^(?:OLDAP-2\.2\.1)/,/^(?:CNRI-Python)/,/^(?:XFree86-1\.1)/,/^(?:OSET-PL-2\.1)/,/^(?:Apache-2\.0)/,/^(?:Watcom-1\.0)/,/^(?:PostgreSQL)/,/^(?:Python-2\.0)/,/^(?:RHeCos-1\.1)/,/^(?:EUDatagrid)/,/^(?:Spencer-99)/,/^(?:Intel-ACPI)/,/^(?:CECILL-1\.0)/,/^(?:CECILL-1\.1)/,/^(?:JasPer-2\.0)/,/^(?:CECILL-2\.0)/,/^(?:CECILL-2\.1)/,/^(?:gSOAP-1\.3b)/,/^(?:Spencer-94)/,/^(?:Apache-1\.1)/,/^(?:Spencer-86)/,/^(?:Apache-1\.0)/,/^(?:ClArtistic)/,/^(?:TORQUE-1\.1)/,/^(?:CATOSL-1\.1)/,/^(?:Adobe-2006)/,/^(?:Zimbra-1\.4)/,/^(?:Zimbra-1\.3)/,/^(?:Condor-1\.1)/,/^(?:CC-BY-3\.0)/,/^(?:CC-BY-2\.5)/,/^(?:OLDAP-2\.4)/,/^(?:SGI-B-1\.1)/,/^(?:SISSL-1\.2)/,/^(?:SGI-B-1\.0)/,/^(?:OLDAP-2\.3)/,/^(?:CC-BY-4\.0)/,/^(?:Crossword)/,/^(?:SimPL-2\.0)/,/^(?:OLDAP-2\.2)/,/^(?:OLDAP-2\.1)/,/^(?:ErlPL-1\.1)/,/^(?:LPPL-1\.3a)/,/^(?:LPPL-1\.3c)/,/^(?:OLDAP-2\.0)/,/^(?:Leptonica)/,/^(?:CPOL-1\.02)/,/^(?:OLDAP-1\.4)/,/^(?:OLDAP-1\.3)/,/^(?:CC-BY-2\.0)/,/^(?:Unlicense)/,/^(?:OLDAP-2\.8)/,/^(?:OLDAP-1\.2)/,/^(?:MakeIndex)/,/^(?:OLDAP-2\.7)/,/^(?:OLDAP-1\.1)/,/^(?:Sleepycat)/,/^(?:D-FSL-1\.0)/,/^(?:CC-BY-1\.0)/,/^(?:OLDAP-2\.6)/,/^(?:WXwindows)/,/^(?:NPOSL-3\.0)/,/^(?:FreeImage)/,/^(?:SGI-B-2\.0)/,/^(?:OLDAP-2\.5)/,/^(?:Beerware)/,/^(?:Newsletr)/,/^(?:NBPL-1\.0)/,/^(?:NASA-1\.3)/,/^(?:NLOD-1\.0)/,/^(?:AGPL-1\.0)/,/^(?:OCLC-2\.0)/,/^(?:ODbL-1\.0)/,/^(?:PDDL-1\.0)/,/^(?:Motosoto)/,/^(?:Afmparse)/,/^(?:ANTLR-PD)/,/^(?:LPL-1\.02)/,/^(?:Abstyles)/,/^(?:eCos-2\.0)/,/^(?:APSL-1\.0)/,/^(?:LPPL-1\.2)/,/^(?:LPPL-1\.1)/,/^(?:LPPL-1\.0)/,/^(?:APSL-1\.1)/,/^(?:APSL-2\.0)/,/^(?:Info-ZIP)/,/^(?:Zend-2\.0)/,/^(?:IBM-pibs)/,/^(?:LGPL-2\.0)/,/^(?:LGPL-3\.0)/,/^(?:LGPL-2\.1)/,/^(?:GFDL-1\.3)/,/^(?:PHP-3\.01)/,/^(?:GFDL-1\.2)/,/^(?:GFDL-1\.1)/,/^(?:AGPL-3\.0)/,/^(?:Giftware)/,/^(?:EUPL-1\.1)/,/^(?:RPSL-1\.0)/,/^(?:EUPL-1\.0)/,/^(?:MIT-enna)/,/^(?:CECILL-B)/,/^(?:diffmark)/,/^(?:CECILL-C)/,/^(?:CDDL-1\.0)/,/^(?:Sendmail)/,/^(?:CDDL-1\.1)/,/^(?:CPAL-1\.0)/,/^(?:APSL-1\.2)/,/^(?:NPL-1\.1)/,/^(?:AFL-1\.2)/,/^(?:Caldera)/,/^(?:AFL-2\.0)/,/^(?:FSFULLR)/,/^(?:AFL-2\.1)/,/^(?:VSL-1\.0)/,/^(?:VOSTROM)/,/^(?:UPL-1\.0)/,/^(?:Dotseqn)/,/^(?:CPL-1\.0)/,/^(?:dvipdfm)/,/^(?:EPL-1\.0)/,/^(?:OCCT-PL)/,/^(?:ECL-1\.0)/,/^(?:Latex2e)/,/^(?:ECL-2\.0)/,/^(?:GPL-1\.0)/,/^(?:GPL-2\.0)/,/^(?:GPL-3\.0)/,/^(?:AFL-3\.0)/,/^(?:LAL-1\.2)/,/^(?:LAL-1\.3)/,/^(?:EFL-1\.0)/,/^(?:EFL-2\.0)/,/^(?:gnuplot)/,/^(?:Aladdin)/,/^(?:LPL-1\.0)/,/^(?:libtiff)/,/^(?:Entessa)/,/^(?:AMDPLPA)/,/^(?:IPL-1\.0)/,/^(?:OPL-1\.0)/,/^(?:OSL-1\.0)/,/^(?:OSL-1\.1)/,/^(?:OSL-2\.0)/,/^(?:OSL-2\.1)/,/^(?:OSL-3\.0)/,/^(?:OpenSSL)/,/^(?:ZPL-2\.1)/,/^(?:PHP-3\.0)/,/^(?:ZPL-2\.0)/,/^(?:ZPL-1\.1)/,/^(?:CC0-1\.0)/,/^(?:SPL-1\.0)/,/^(?:psutils)/,/^(?:MPL-1\.0)/,/^(?:QPL-1\.0)/,/^(?:MPL-1\.1)/,/^(?:MPL-2\.0)/,/^(?:APL-1\.0)/,/^(?:RPL-1\.1)/,/^(?:RPL-1\.5)/,/^(?:MIT-CMU)/,/^(?:Multics)/,/^(?:Eurosym)/,/^(?:BSL-1\.0)/,/^(?:MIT-feh)/,/^(?:Saxpath)/,/^(?:Borceux)/,/^(?:OFL-1\.1)/,/^(?:OFL-1\.0)/,/^(?:AFL-1\.1)/,/^(?:YPL-1\.1)/,/^(?:YPL-1\.0)/,/^(?:NPL-1\.0)/,/^(?:iMatix)/,/^(?:mpich2)/,/^(?:APAFML)/,/^(?:Bahyph)/,/^(?:RSA-MD)/,/^(?:psfrag)/,/^(?:Plexus)/,/^(?:eGenix)/,/^(?:Glulxe)/,/^(?:SAX-PD)/,/^(?:Imlib2)/,/^(?:Wsuipa)/,/^(?:LGPLLR)/,/^(?:Libpng)/,/^(?:xinetd)/,/^(?:MITNFA)/,/^(?:NetCDF)/,/^(?:Naumen)/,/^(?:SMPPL)/,/^(?:Nunit)/,/^(?:FSFUL)/,/^(?:GL2PS)/,/^(?:SMLNJ)/,/^(?:Rdisc)/,/^(?:Noweb)/,/^(?:Nokia)/,/^(?:SISSL)/,/^(?:Qhull)/,/^(?:Intel)/,/^(?:Glide)/,/^(?:Xerox)/,/^(?:AMPAS)/,/^(?:WTFPL)/,/^(?:MS-PL)/,/^(?:XSkat)/,/^(?:MS-RL)/,/^(?:MirOS)/,/^(?:RSCPL)/,/^(?:TMate)/,/^(?:OGTSL)/,/^(?:FSFAP)/,/^(?:NCSA)/,/^(?:Zlib)/,/^(?:SCEA)/,/^(?:SNIA)/,/^(?:NGPL)/,/^(?:NOSL)/,/^(?:ADSL)/,/^(?:MTLL)/,/^(?:NLPL)/,/^(?:Ruby)/,/^(?:JSON)/,/^(?:Barr)/,/^(?:0BSD)/,/^(?:Xnet)/,/^(?:Cube)/,/^(?:curl)/,/^(?:DSDP)/,/^(?:Fair)/,/^(?:HPND)/,/^(?:TOSL)/,/^(?:IJG)/,/^(?:SWL)/,/^(?:Vim)/,/^(?:FTL)/,/^(?:ICU)/,/^(?:OML)/,/^(?:NRL)/,/^(?:DOC)/,/^(?:TCL)/,/^(?:W3C)/,/^(?:NTP)/,/^(?:IPA)/,/^(?:ISC)/,/^(?:X11)/,/^(?:AAL)/,/^(?:AML)/,/^(?:xpp)/,/^(?:Zed)/,/^(?:MIT)/,/^(?:Mup)/], -conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364],"inclusive":true}} -}); -return lexer; -})(); -parser.lexer = lexer; -function Parser () { - this.yy = {}; + +function textFormatter(options) { + return through(textify); + + function textify(logEvent) { + var line = util.format('%s%s', textifyTags(logEvent.tag), + logEvent.msg.toString()); + if (options.timeStamp) { + line = util.format('%s %s', new Date(logEvent.time).toISOString(), line); + } + this.emit('data', line.replace(/\n/g, '\\n')); + } + + function textifyTags(tags) { + var str = ''; + if (typeof tags === 'string') { + str = tags + ' '; + } else if (typeof tags === 'object') { + for (var t in tags) { + str += t + ':' + tags[t] + ' '; + } + } + return str; + } } -Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})(); +function jsonFormatter(options) { + return through(jsonify); -if (true) { -exports.parser = spdxparse; -exports.Parser = spdxparse.Parser; -exports.parse = function () { return spdxparse.parse.apply(spdxparse, arguments); }; -exports.main = function commonjsMain(args) { - if (!args[1]) { - console.log('Usage: '+args[0]+' FILE'); - process.exit(1); + function jsonify(logEvent) { + if (options.timeStamp) { + logEvent.time = new Date(logEvent.time).toISOString(); + } else { + delete logEvent.time; } - var source = __webpack_require__("fs").readFileSync(__webpack_require__("path").normalize(args[1]), "utf8"); - return exports.parser.parse(source); -}; -if ( true && __webpack_require__.c[__webpack_require__.s] === module) { - exports.main(process.argv.slice(1)); + logEvent.msg = logEvent.msg.toString(); + this.emit('data', JSON.stringify(logEvent)); + } } + +function lineMerger(host) { + var previousLine = null; + var flushTimer = null; + var stream = through(lineMergerWrite, lineMergerEnd); + var flush = _flush.bind(stream); + + return stream; + + function lineMergerWrite(line) { + if (/^\s+/.test(line.msg)) { + if (previousLine) { + previousLine.msg += '\n' + line.msg; + } else { + previousLine = line; + } + } else { + flush(); + previousLine = line; + } + // rolling timeout + clearTimeout(flushTimer); + flushTimer = setTimeout(flush.bind(this), 10); + } + + function _flush() { + if (previousLine) { + this.emit('data', previousLine); + previousLine = null; + } + } + + function lineMergerEnd() { + flush.call(this); + this.emit('end'); + } } -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__("../../node_modules/webpack/buildin/module.js")(module))) /***/ }), -/***/ "../../node_modules/spdx-license-ids/spdx-license-ids.json": -/***/ (function(module) { +/***/ "../../node_modules/strong-log-transformer/package.json": +/***/ (function(module) { + +module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); + +/***/ }), + +/***/ "../../node_modules/supports-color/index.js": +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const os = __webpack_require__("os"); +const tty = __webpack_require__("tty"); +const hasFlag = __webpack_require__("../../node_modules/has-flag/index.js"); + +const {env} = process; + +let forceColor; +if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false') || + hasFlag('color=never')) { + forceColor = 0; +} else if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + forceColor = 1; +} + +if ('FORCE_COLOR' in env) { + if (env.FORCE_COLOR === 'true') { + forceColor = 1; + } else if (env.FORCE_COLOR === 'false') { + forceColor = 0; + } else { + forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3); + } +} -module.exports = JSON.parse("[\"Glide\",\"Abstyles\",\"AFL-1.1\",\"AFL-1.2\",\"AFL-2.0\",\"AFL-2.1\",\"AFL-3.0\",\"AMPAS\",\"APL-1.0\",\"Adobe-Glyph\",\"APAFML\",\"Adobe-2006\",\"AGPL-1.0\",\"Afmparse\",\"Aladdin\",\"ADSL\",\"AMDPLPA\",\"ANTLR-PD\",\"Apache-1.0\",\"Apache-1.1\",\"Apache-2.0\",\"AML\",\"APSL-1.0\",\"APSL-1.1\",\"APSL-1.2\",\"APSL-2.0\",\"Artistic-1.0\",\"Artistic-1.0-Perl\",\"Artistic-1.0-cl8\",\"Artistic-2.0\",\"AAL\",\"Bahyph\",\"Barr\",\"Beerware\",\"BitTorrent-1.0\",\"BitTorrent-1.1\",\"BSL-1.0\",\"Borceux\",\"BSD-2-Clause\",\"BSD-2-Clause-FreeBSD\",\"BSD-2-Clause-NetBSD\",\"BSD-3-Clause\",\"BSD-3-Clause-Clear\",\"BSD-4-Clause\",\"BSD-Protection\",\"BSD-Source-Code\",\"BSD-3-Clause-Attribution\",\"0BSD\",\"BSD-4-Clause-UC\",\"bzip2-1.0.5\",\"bzip2-1.0.6\",\"Caldera\",\"CECILL-1.0\",\"CECILL-1.1\",\"CECILL-2.0\",\"CECILL-2.1\",\"CECILL-B\",\"CECILL-C\",\"ClArtistic\",\"MIT-CMU\",\"CNRI-Jython\",\"CNRI-Python\",\"CNRI-Python-GPL-Compatible\",\"CPOL-1.02\",\"CDDL-1.0\",\"CDDL-1.1\",\"CPAL-1.0\",\"CPL-1.0\",\"CATOSL-1.1\",\"Condor-1.1\",\"CC-BY-1.0\",\"CC-BY-2.0\",\"CC-BY-2.5\",\"CC-BY-3.0\",\"CC-BY-4.0\",\"CC-BY-ND-1.0\",\"CC-BY-ND-2.0\",\"CC-BY-ND-2.5\",\"CC-BY-ND-3.0\",\"CC-BY-ND-4.0\",\"CC-BY-NC-1.0\",\"CC-BY-NC-2.0\",\"CC-BY-NC-2.5\",\"CC-BY-NC-3.0\",\"CC-BY-NC-4.0\",\"CC-BY-NC-ND-1.0\",\"CC-BY-NC-ND-2.0\",\"CC-BY-NC-ND-2.5\",\"CC-BY-NC-ND-3.0\",\"CC-BY-NC-ND-4.0\",\"CC-BY-NC-SA-1.0\",\"CC-BY-NC-SA-2.0\",\"CC-BY-NC-SA-2.5\",\"CC-BY-NC-SA-3.0\",\"CC-BY-NC-SA-4.0\",\"CC-BY-SA-1.0\",\"CC-BY-SA-2.0\",\"CC-BY-SA-2.5\",\"CC-BY-SA-3.0\",\"CC-BY-SA-4.0\",\"CC0-1.0\",\"Crossword\",\"CrystalStacker\",\"CUA-OPL-1.0\",\"Cube\",\"curl\",\"D-FSL-1.0\",\"diffmark\",\"WTFPL\",\"DOC\",\"Dotseqn\",\"DSDP\",\"dvipdfm\",\"EPL-1.0\",\"ECL-1.0\",\"ECL-2.0\",\"eGenix\",\"EFL-1.0\",\"EFL-2.0\",\"MIT-advertising\",\"MIT-enna\",\"Entessa\",\"ErlPL-1.1\",\"EUDatagrid\",\"EUPL-1.0\",\"EUPL-1.1\",\"Eurosym\",\"Fair\",\"MIT-feh\",\"Frameworx-1.0\",\"FreeImage\",\"FTL\",\"FSFAP\",\"FSFUL\",\"FSFULLR\",\"Giftware\",\"GL2PS\",\"Glulxe\",\"AGPL-3.0\",\"GFDL-1.1\",\"GFDL-1.2\",\"GFDL-1.3\",\"GPL-1.0\",\"GPL-2.0\",\"GPL-3.0\",\"LGPL-2.1\",\"LGPL-3.0\",\"LGPL-2.0\",\"gnuplot\",\"gSOAP-1.3b\",\"HaskellReport\",\"HPND\",\"IBM-pibs\",\"IPL-1.0\",\"ICU\",\"ImageMagick\",\"iMatix\",\"Imlib2\",\"IJG\",\"Info-ZIP\",\"Intel-ACPI\",\"Intel\",\"Interbase-1.0\",\"IPA\",\"ISC\",\"JasPer-2.0\",\"JSON\",\"LPPL-1.0\",\"LPPL-1.1\",\"LPPL-1.2\",\"LPPL-1.3a\",\"LPPL-1.3c\",\"Latex2e\",\"BSD-3-Clause-LBNL\",\"Leptonica\",\"LGPLLR\",\"Libpng\",\"libtiff\",\"LAL-1.2\",\"LAL-1.3\",\"LiLiQ-P-1.1\",\"LiLiQ-Rplus-1.1\",\"LiLiQ-R-1.1\",\"LPL-1.02\",\"LPL-1.0\",\"MakeIndex\",\"MTLL\",\"MS-PL\",\"MS-RL\",\"MirOS\",\"MITNFA\",\"MIT\",\"Motosoto\",\"MPL-1.0\",\"MPL-1.1\",\"MPL-2.0\",\"MPL-2.0-no-copyleft-exception\",\"mpich2\",\"Multics\",\"Mup\",\"NASA-1.3\",\"Naumen\",\"NBPL-1.0\",\"NetCDF\",\"NGPL\",\"NOSL\",\"NPL-1.0\",\"NPL-1.1\",\"Newsletr\",\"NLPL\",\"Nokia\",\"NPOSL-3.0\",\"NLOD-1.0\",\"Noweb\",\"NRL\",\"NTP\",\"Nunit\",\"OCLC-2.0\",\"ODbL-1.0\",\"PDDL-1.0\",\"OCCT-PL\",\"OGTSL\",\"OLDAP-2.2.2\",\"OLDAP-1.1\",\"OLDAP-1.2\",\"OLDAP-1.3\",\"OLDAP-1.4\",\"OLDAP-2.0\",\"OLDAP-2.0.1\",\"OLDAP-2.1\",\"OLDAP-2.2\",\"OLDAP-2.2.1\",\"OLDAP-2.3\",\"OLDAP-2.4\",\"OLDAP-2.5\",\"OLDAP-2.6\",\"OLDAP-2.7\",\"OLDAP-2.8\",\"OML\",\"OPL-1.0\",\"OSL-1.0\",\"OSL-1.1\",\"OSL-2.0\",\"OSL-2.1\",\"OSL-3.0\",\"OpenSSL\",\"OSET-PL-2.1\",\"PHP-3.0\",\"PHP-3.01\",\"Plexus\",\"PostgreSQL\",\"psfrag\",\"psutils\",\"Python-2.0\",\"QPL-1.0\",\"Qhull\",\"Rdisc\",\"RPSL-1.0\",\"RPL-1.1\",\"RPL-1.5\",\"RHeCos-1.1\",\"RSCPL\",\"RSA-MD\",\"Ruby\",\"SAX-PD\",\"Saxpath\",\"SCEA\",\"SWL\",\"SMPPL\",\"Sendmail\",\"SGI-B-1.0\",\"SGI-B-1.1\",\"SGI-B-2.0\",\"OFL-1.0\",\"OFL-1.1\",\"SimPL-2.0\",\"Sleepycat\",\"SNIA\",\"Spencer-86\",\"Spencer-94\",\"Spencer-99\",\"SMLNJ\",\"SugarCRM-1.1.3\",\"SISSL\",\"SISSL-1.2\",\"SPL-1.0\",\"Watcom-1.0\",\"TCL\",\"Unlicense\",\"TMate\",\"TORQUE-1.1\",\"TOSL\",\"Unicode-TOU\",\"UPL-1.0\",\"NCSA\",\"Vim\",\"VOSTROM\",\"VSL-1.0\",\"W3C-19980720\",\"W3C\",\"Wsuipa\",\"Xnet\",\"X11\",\"Xerox\",\"XFree86-1.1\",\"xinetd\",\"xpp\",\"XSkat\",\"YPL-1.0\",\"YPL-1.1\",\"Zed\",\"Zend-2.0\",\"Zimbra-1.3\",\"Zimbra-1.4\",\"Zlib\",\"zlib-acknowledgement\",\"ZPL-1.1\",\"ZPL-2.0\",\"ZPL-2.1\",\"BSD-3-Clause-No-Nuclear-License\",\"BSD-3-Clause-No-Nuclear-Warranty\",\"BSD-3-Clause-No-Nuclear-License-2014\",\"eCos-2.0\",\"GPL-2.0-with-autoconf-exception\",\"GPL-2.0-with-bison-exception\",\"GPL-2.0-with-classpath-exception\",\"GPL-2.0-with-font-exception\",\"GPL-2.0-with-GCC-exception\",\"GPL-3.0-with-autoconf-exception\",\"GPL-3.0-with-GCC-exception\",\"StandardML-NJ\",\"WXwindows\"]"); +function translateLevel(level) { + if (level === 0) { + return false; + } -/***/ }), + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} -/***/ "../../node_modules/strip-ansi/index.js": -/***/ (function(module, exports, __webpack_require__) { +function supportsColor(haveStream, streamIsTTY) { + if (forceColor === 0) { + return 0; + } -"use strict"; + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } -const ansiRegex = __webpack_require__("../../node_modules/ansi-regex/index.js"); + if (hasFlag('color=256')) { + return 2; + } -module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; + if (haveStream && !streamIsTTY && forceColor === undefined) { + return 0; + } + const min = forceColor || 0; -/***/ }), + if (env.TERM === 'dumb') { + return min; + } -/***/ "../../node_modules/strip-bom/index.js": -/***/ (function(module, exports, __webpack_require__) { + if (process.platform === 'win32') { + // Windows 10 build 10586 is the first Windows release that supports 256 colors. + // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(osRelease[0]) >= 10 && + Number(osRelease[2]) >= 10586 + ) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } -"use strict"; + return 1; + } + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } -module.exports = string => { - if (typeof string !== 'string') { - throw new TypeError(`Expected a string, got ${typeof string}`); + return min; } - // Catches EFBBBF (UTF-8 BOM) because the buffer-to-string - // conversion translates it to FEFF (UTF-16 BOM) - if (string.charCodeAt(0) === 0xFEFF) { - return string.slice(1); + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; } - return string; -}; - - -/***/ }), + if ('GITHUB_ACTIONS' in env) { + return 1; + } -/***/ "../../node_modules/strip-final-newline/index.js": -/***/ (function(module, exports, __webpack_require__) { + if (env.COLORTERM === 'truecolor') { + return 3; + } -"use strict"; + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Apple_Terminal': + return 2; + // No default + } + } -module.exports = input => { - const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); - const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } - if (input[input.length - 1] === LF) { - input = input.slice(0, input.length - 1); + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; } - if (input[input.length - 1] === CR) { - input = input.slice(0, input.length - 1); + if ('COLORTERM' in env) { + return 1; } - return input; + return min; +} + +function getSupportLevel(stream) { + const level = supportsColor(stream, stream && stream.isTTY); + return translateLevel(level); +} + +module.exports = { + supportsColor: getSupportLevel, + stdout: translateLevel(supportsColor(true, tty.isatty(1))), + stderr: translateLevel(supportsColor(true, tty.isatty(2))) }; /***/ }), -/***/ "../../node_modules/strong-log-transformer/index.js": +/***/ "../../node_modules/through/index.js": /***/ (function(module, exports, __webpack_require__) { -// Copyright IBM Corp. 2014,2018. All Rights Reserved. -// Node module: strong-log-transformer -// This file is licensed under the Apache License 2.0. -// License text available at https://opensource.org/licenses/Apache-2.0 +var Stream = __webpack_require__("stream") -module.exports = __webpack_require__("../../node_modules/strong-log-transformer/lib/logger.js"); -module.exports.cli = __webpack_require__("../../node_modules/strong-log-transformer/lib/cli.js"); +// through +// +// a stream that does nothing but re-emit the input. +// useful for aggregating a series of changing but not ending streams into one stream) +exports = module.exports = through +through.through = through -/***/ }), +//create a readable writable stream. -/***/ "../../node_modules/strong-log-transformer/lib/cli.js": -/***/ (function(module, exports, __webpack_require__) { +function through (write, end, opts) { + write = write || function (data) { this.queue(data) } + end = end || function () { this.queue(null) } -"use strict"; -// Copyright IBM Corp. 2014,2018. All Rights Reserved. -// Node module: strong-log-transformer -// This file is licensed under the Apache License 2.0. -// License text available at https://opensource.org/licenses/Apache-2.0 + var ended = false, destroyed = false, buffer = [], _ended = false + var stream = new Stream() + stream.readable = stream.writable = true + stream.paused = false +// stream.autoPause = !(opts && opts.autoPause === false) + stream.autoDestroy = !(opts && opts.autoDestroy === false) + stream.write = function (data) { + write.call(this, data) + return !stream.paused + } -var minimist = __webpack_require__("../../node_modules/minimist/index.js"); -var path = __webpack_require__("path"); + function drain() { + while(buffer.length && !stream.paused) { + var data = buffer.shift() + if(null === data) + return stream.emit('end') + else + stream.emit('data', data) + } + } -var Logger = __webpack_require__("../../node_modules/strong-log-transformer/lib/logger.js"); -var pkg = __webpack_require__("../../node_modules/strong-log-transformer/package.json"); + stream.queue = stream.push = function (data) { +// console.error(ended) + if(_ended) return stream + if(data === null) _ended = true + buffer.push(data) + drain() + return stream + } -module.exports = cli; + //this will be registered as the first 'end' listener + //must call destroy next tick, to make sure we're after any + //stream piped from here. + //this is only a problem if end is not emitted synchronously. + //a nicer way to do this is to make sure this is the last listener for 'end' -function cli(args) { - var opts = minimist(args.slice(2)); - var $0 = path.basename(args[1]); - var p = console.log.bind(console); - if (opts.v || opts.version) { - version($0, p); - } else if (opts.h || opts.help) { - usage($0, p); - } else if (args.length < 3) { - process.stdin.pipe(Logger()).pipe(process.stdout); - } else { - process.stdin.pipe(Logger(opts)).pipe(process.stdout); + stream.on('end', function () { + stream.readable = false + if(!stream.writable && stream.autoDestroy) + process.nextTick(function () { + stream.destroy() + }) + }) + + function _end () { + stream.writable = false + end.call(stream) + if(!stream.readable && stream.autoDestroy) + stream.destroy() } -} -function version($0, p) { - p('%s v%s', pkg.name, pkg.version); -} + stream.end = function (data) { + if(ended) return + ended = true + if(arguments.length) stream.write(data) + _end() // will emit or queue + return stream + } -function usage($0, p) { - var PADDING = ' '; - var opt, def; - p('Usage: %s [options]', $0); - p(''); - p('%s', pkg.description); - p(''); - p('OPTIONS:'); - for (opt in Logger.DEFAULTS) { - def = Logger.DEFAULTS[opt]; - if (typeof def === 'boolean') - boolOpt(opt, Logger.DEFAULTS[opt]); - else - stdOpt(opt, Logger.DEFAULTS[opt]); + stream.destroy = function () { + if(destroyed) return + destroyed = true + ended = true + buffer.length = 0 + stream.writable = stream.readable = false + stream.emit('close') + return stream } - p(''); - function boolOpt(name, def) { - name = name + PADDING.slice(0, 20-name.length); - p(' --%s default: %s', name, def); + stream.pause = function () { + if(stream.paused) return + stream.paused = true + return stream } - function stdOpt(name, def) { - var value = name.toUpperCase() + - PADDING.slice(0, 19 - name.length*2); - p(' --%s %s default: %j', name, value, def); + stream.resume = function () { + if(stream.paused) { + stream.paused = false + stream.emit('resume') + } + drain() + //may have become paused again, + //as drain emits 'data'. + if(!stream.paused) + stream.emit('drain') + return stream } + return stream } + /***/ }), -/***/ "../../node_modules/strong-log-transformer/lib/logger.js": +/***/ "../../node_modules/to-regex-range/index.js": /***/ (function(module, exports, __webpack_require__) { "use strict"; -// Copyright IBM Corp. 2014,2018. All Rights Reserved. -// Node module: strong-log-transformer -// This file is licensed under the Apache License 2.0. -// License text available at https://opensource.org/licenses/Apache-2.0 - - - -var stream = __webpack_require__("stream"); -var util = __webpack_require__("util"); -var fs = __webpack_require__("fs"); - -var through = __webpack_require__("../../node_modules/through/index.js"); -var duplexer = __webpack_require__("../../node_modules/duplexer/index.js"); -var StringDecoder = __webpack_require__("string_decoder").StringDecoder; +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ -module.exports = Logger; -Logger.DEFAULTS = { - format: 'text', - tag: '', - mergeMultiline: false, - timeStamp: false, -}; -var formatters = { - text: textFormatter, - json: jsonFormatter, -} +const isNumber = __webpack_require__("../../node_modules/to-regex-range/node_modules/is-number/index.js"); -function Logger(options) { - var defaults = JSON.parse(JSON.stringify(Logger.DEFAULTS)); - options = util._extend(defaults, options || {}); - var catcher = deLiner(); - var emitter = catcher; - var transforms = [ - objectifier(), - ]; +const toRegexRange = (min, max, options) => { + if (isNumber(min) === false) { + throw new TypeError('toRegexRange: expected the first argument to be a number'); + } - if (options.tag) { - transforms.push(staticTagger(options.tag)); + if (max === void 0 || min === max) { + return String(min); } - if (options.mergeMultiline) { - transforms.push(lineMerger()); + if (isNumber(max) === false) { + throw new TypeError('toRegexRange: expected the second argument to be a number.'); } - // TODO - // if (options.pidStamp) { - // transforms.push(pidStamper(options.pid)); - // } + let opts = { relaxZeros: true, ...options }; + if (typeof opts.strictZeros === 'boolean') { + opts.relaxZeros = opts.strictZeros === false; + } - // TODO - // if (options.workerStamp) { - // transforms.push(workerStamper(options.worker)); - // } + let relax = String(opts.relaxZeros); + let shorthand = String(opts.shorthand); + let capture = String(opts.capture); + let wrap = String(opts.wrap); + let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; - transforms.push(formatters[options.format](options)); + if (toRegexRange.cache.hasOwnProperty(cacheKey)) { + return toRegexRange.cache[cacheKey].result; + } - // restore line endings that were removed by line splitting - transforms.push(reLiner()); + let a = Math.min(min, max); + let b = Math.max(min, max); - for (var t in transforms) { - emitter = emitter.pipe(transforms[t]); + if (Math.abs(a - b) === 1) { + let result = min + '|' + max; + if (opts.capture) { + return `(${result})`; + } + if (opts.wrap === false) { + return result; + } + return `(?:${result})`; } - return duplexer(catcher, emitter); -} - -function deLiner() { - var decoder = new StringDecoder('utf8'); - var last = ''; + let isPadded = hasPadding(min) || hasPadding(max); + let state = { min, max, a, b }; + let positives = []; + let negatives = []; - return new stream.Transform({ - transform(chunk, _enc, callback) { - last += decoder.write(chunk); - var list = last.split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g); - last = list.pop(); - for (var i = 0; i < list.length; i++) { - // swallow empty lines - if (list[i]) { - this.push(list[i]); - } - } - callback(); - }, - flush(callback) { - // incomplete UTF8 sequences become UTF8 replacement characters - last += decoder.end(); - if (last) { - this.push(last); - } - callback(); - }, - }); -} + if (isPadded) { + state.isPadded = isPadded; + state.maxLen = String(state.max).length; + } -function reLiner() { - return through(appendNewline); + if (a < 0) { + let newMin = b < 0 ? Math.abs(b) : 1; + negatives = splitToPatterns(newMin, Math.abs(a), state, opts); + a = state.a = 0; + } - function appendNewline(line) { - this.emit('data', line + '\n'); + if (b >= 0) { + positives = splitToPatterns(a, b, state, opts); } -} -function objectifier() { - return through(objectify, null, {autoDestroy: false}); + state.negatives = negatives; + state.positives = positives; + state.result = collatePatterns(negatives, positives, opts); - function objectify(line) { - this.emit('data', { - msg: line, - time: Date.now(), - }); + if (opts.capture === true) { + state.result = `(${state.result})`; + } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { + state.result = `(?:${state.result})`; } -} -function staticTagger(tag) { - return through(tagger); + toRegexRange.cache[cacheKey] = state; + return state.result; +}; - function tagger(logEvent) { - logEvent.tag = tag; - this.emit('data', logEvent); - } +function collatePatterns(neg, pos, options) { + let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; + let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; + let intersected = filterPatterns(neg, pos, '-?', true, options) || []; + let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); + return subpatterns.join('|'); } -function textFormatter(options) { - return through(textify); +function splitToRanges(min, max) { + let nines = 1; + let zeros = 1; - function textify(logEvent) { - var line = util.format('%s%s', textifyTags(logEvent.tag), - logEvent.msg.toString()); - if (options.timeStamp) { - line = util.format('%s %s', new Date(logEvent.time).toISOString(), line); - } - this.emit('data', line.replace(/\n/g, '\\n')); + let stop = countNines(min, nines); + let stops = new Set([max]); + + while (min <= stop && stop <= max) { + stops.add(stop); + nines += 1; + stop = countNines(min, nines); } - function textifyTags(tags) { - var str = ''; - if (typeof tags === 'string') { - str = tags + ' '; - } else if (typeof tags === 'object') { - for (var t in tags) { - str += t + ':' + tags[t] + ' '; - } - } - return str; + stop = countZeros(max + 1, zeros) - 1; + + while (min < stop && stop <= max) { + stops.add(stop); + zeros += 1; + stop = countZeros(max + 1, zeros) - 1; } + + stops = [...stops]; + stops.sort(compare); + return stops; } -function jsonFormatter(options) { - return through(jsonify); +/** + * Convert a range to a regex pattern + * @param {Number} `start` + * @param {Number} `stop` + * @return {String} + */ - function jsonify(logEvent) { - if (options.timeStamp) { - logEvent.time = new Date(logEvent.time).toISOString(); - } else { - delete logEvent.time; - } - logEvent.msg = logEvent.msg.toString(); - this.emit('data', JSON.stringify(logEvent)); +function rangeToPattern(start, stop, options) { + if (start === stop) { + return { pattern: start, count: [], digits: 0 }; } -} -function lineMerger(host) { - var previousLine = null; - var flushTimer = null; - var stream = through(lineMergerWrite, lineMergerEnd); - var flush = _flush.bind(stream); + let zipped = zip(start, stop); + let digits = zipped.length; + let pattern = ''; + let count = 0; + + for (let i = 0; i < digits; i++) { + let [startDigit, stopDigit] = zipped[i]; + + if (startDigit === stopDigit) { + pattern += startDigit; - return stream; + } else if (startDigit !== '0' || stopDigit !== '9') { + pattern += toCharacterClass(startDigit, stopDigit, options); - function lineMergerWrite(line) { - if (/^\s+/.test(line.msg)) { - if (previousLine) { - previousLine.msg += '\n' + line.msg; - } else { - previousLine = line; - } } else { - flush(); - previousLine = line; + count++; } - // rolling timeout - clearTimeout(flushTimer); - flushTimer = setTimeout(flush.bind(this), 10); } - function _flush() { - if (previousLine) { - this.emit('data', previousLine); - previousLine = null; - } + if (count) { + pattern += options.shorthand === true ? '\\d' : '[0-9]'; } - function lineMergerEnd() { - flush.call(this); - this.emit('end'); - } + return { pattern, count: [count], digits }; } +function splitToPatterns(min, max, tok, options) { + let ranges = splitToRanges(min, max); + let tokens = []; + let start = min; + let prev; -/***/ }), + for (let i = 0; i < ranges.length; i++) { + let max = ranges[i]; + let obj = rangeToPattern(String(start), String(max), options); + let zeros = ''; -/***/ "../../node_modules/strong-log-transformer/package.json": -/***/ (function(module) { + if (!tok.isPadded && prev && prev.pattern === obj.pattern) { + if (prev.count.length > 1) { + prev.count.pop(); + } -module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); + prev.count.push(obj.count[0]); + prev.string = prev.pattern + toQuantifier(prev.count); + start = max + 1; + continue; + } -/***/ }), + if (tok.isPadded) { + zeros = padZeros(max, tok, options); + } -/***/ "../../node_modules/supports-color/index.js": -/***/ (function(module, exports, __webpack_require__) { + obj.string = zeros + obj.pattern + toQuantifier(obj.count); + tokens.push(obj); + start = max + 1; + prev = obj; + } -"use strict"; + return tokens; +} -const os = __webpack_require__("os"); -const tty = __webpack_require__("tty"); -const hasFlag = __webpack_require__("../../node_modules/has-flag/index.js"); +function filterPatterns(arr, comparison, prefix, intersection, options) { + let result = []; -const {env} = process; + for (let ele of arr) { + let { string } = ele; -let forceColor; -if (hasFlag('no-color') || - hasFlag('no-colors') || - hasFlag('color=false') || - hasFlag('color=never')) { - forceColor = 0; -} else if (hasFlag('color') || - hasFlag('colors') || - hasFlag('color=true') || - hasFlag('color=always')) { - forceColor = 1; -} + // only push if _both_ are negative... + if (!intersection && !contains(comparison, 'string', string)) { + result.push(prefix + string); + } -if ('FORCE_COLOR' in env) { - if (env.FORCE_COLOR === 'true') { - forceColor = 1; - } else if (env.FORCE_COLOR === 'false') { - forceColor = 0; - } else { - forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3); - } + // or _both_ are positive + if (intersection && contains(comparison, 'string', string)) { + result.push(prefix + string); + } + } + return result; } -function translateLevel(level) { - if (level === 0) { - return false; - } +/** + * Zip strings + */ - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3 - }; +function zip(a, b) { + let arr = []; + for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); + return arr; } -function supportsColor(haveStream, streamIsTTY) { - if (forceColor === 0) { - return 0; - } - - if (hasFlag('color=16m') || - hasFlag('color=full') || - hasFlag('color=truecolor')) { - return 3; - } +function compare(a, b) { + return a > b ? 1 : b > a ? -1 : 0; +} - if (hasFlag('color=256')) { - return 2; - } +function contains(arr, key, val) { + return arr.some(ele => ele[key] === val); +} - if (haveStream && !streamIsTTY && forceColor === undefined) { - return 0; - } +function countNines(min, len) { + return Number(String(min).slice(0, -len) + '9'.repeat(len)); +} - const min = forceColor || 0; +function countZeros(integer, zeros) { + return integer - (integer % Math.pow(10, zeros)); +} - if (env.TERM === 'dumb') { - return min; - } +function toQuantifier(digits) { + let [start = 0, stop = ''] = digits; + if (stop || start > 1) { + return `{${start + (stop ? ',' + stop : '')}}`; + } + return ''; +} - if (process.platform === 'win32') { - // Windows 10 build 10586 is the first Windows release that supports 256 colors. - // Windows 10 build 14931 is the first release that supports 16m/TrueColor. - const osRelease = os.release().split('.'); - if ( - Number(osRelease[0]) >= 10 && - Number(osRelease[2]) >= 10586 - ) { - return Number(osRelease[2]) >= 14931 ? 3 : 2; - } +function toCharacterClass(a, b, options) { + return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; +} - return 1; - } +function hasPadding(str) { + return /^-?(0+)\d/.test(str); +} - if ('CI' in env) { - if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { - return 1; - } +function padZeros(value, tok, options) { + if (!tok.isPadded) { + return value; + } - return min; - } + let diff = Math.abs(tok.maxLen - String(value).length); + let relax = options.relaxZeros !== false; - if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; - } + switch (diff) { + case 0: + return ''; + case 1: + return relax ? '0?' : '0'; + case 2: + return relax ? '0{0,2}' : '00'; + default: { + return relax ? `0{0,${diff}}` : `0{${diff}}`; + } + } +} - if ('GITHUB_ACTIONS' in env) { - return 1; - } +/** + * Cache + */ - if (env.COLORTERM === 'truecolor') { - return 3; - } +toRegexRange.cache = {}; +toRegexRange.clearCache = () => (toRegexRange.cache = {}); - if ('TERM_PROGRAM' in env) { - const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); +/** + * Expose `toRegexRange` + */ - switch (env.TERM_PROGRAM) { - case 'iTerm.app': - return version >= 3 ? 3 : 2; - case 'Apple_Terminal': - return 2; - // No default - } - } +module.exports = toRegexRange; - if (/-256(color)?$/i.test(env.TERM)) { - return 2; - } - if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { - return 1; - } +/***/ }), - if ('COLORTERM' in env) { - return 1; - } +/***/ "../../node_modules/to-regex-range/node_modules/is-number/index.js": +/***/ (function(module, exports, __webpack_require__) { - return min; -} +"use strict"; +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ -function getSupportLevel(stream) { - const level = supportsColor(stream, stream && stream.isTTY); - return translateLevel(level); -} -module.exports = { - supportsColor: getSupportLevel, - stdout: translateLevel(supportsColor(true, tty.isatty(1))), - stderr: translateLevel(supportsColor(true, tty.isatty(2))) + +module.exports = function(num) { + if (typeof num === 'number') { + return num - num === 0; + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); + } + return false; }; /***/ }), -/***/ "../../node_modules/through/index.js": +/***/ "../../node_modules/validate-npm-package-license/index.js": /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__("stream") - -// through -// -// a stream that does nothing but re-emit the input. -// useful for aggregating a series of changing but not ending streams into one stream) - -exports = module.exports = through -through.through = through - -//create a readable writable stream. +var parse = __webpack_require__("../../node_modules/validate-npm-package-license/node_modules/spdx-expression-parse/index.js"); +var correct = __webpack_require__("../../node_modules/validate-npm-package-license/node_modules/spdx-correct/index.js"); -function through (write, end, opts) { - write = write || function (data) { this.queue(data) } - end = end || function () { this.queue(null) } +var genericWarning = ( + 'license should be ' + + 'a valid SPDX license expression (without "LicenseRef"), ' + + '"UNLICENSED", or ' + + '"SEE LICENSE IN "' +); - var ended = false, destroyed = false, buffer = [], _ended = false - var stream = new Stream() - stream.readable = stream.writable = true - stream.paused = false +var fileReferenceRE = /^SEE LICEN[CS]E IN (.+)$/; -// stream.autoPause = !(opts && opts.autoPause === false) - stream.autoDestroy = !(opts && opts.autoDestroy === false) +function startsWith(prefix, string) { + return string.slice(0, prefix.length) === prefix; +} - stream.write = function (data) { - write.call(this, data) - return !stream.paused +function usesLicenseRef(ast) { + if (ast.hasOwnProperty('license')) { + var license = ast.license; + return ( + startsWith('LicenseRef', license) || + startsWith('DocumentRef', license) + ); + } else { + return ( + usesLicenseRef(ast.left) || + usesLicenseRef(ast.right) + ); } +} - function drain() { - while(buffer.length && !stream.paused) { - var data = buffer.shift() - if(null === data) - return stream.emit('end') - else - stream.emit('data', data) +module.exports = function(argument) { + var ast; + + try { + ast = parse(argument); + } catch (e) { + var match + if ( + argument === 'UNLICENSED' || + argument === 'UNLICENCED' + ) { + return { + validForOldPackages: true, + validForNewPackages: true, + unlicensed: true + }; + } else if (match = fileReferenceRE.exec(argument)) { + return { + validForOldPackages: true, + validForNewPackages: true, + inFile: match[1] + }; + } else { + var result = { + validForOldPackages: false, + validForNewPackages: false, + warnings: [genericWarning] + }; + var corrected = correct(argument); + if (corrected) { + result.warnings.push( + 'license is similar to the valid expression "' + corrected + '"' + ); + } + return result; } } - stream.queue = stream.push = function (data) { -// console.error(ended) - if(_ended) return stream - if(data === null) _ended = true - buffer.push(data) - drain() - return stream + if (usesLicenseRef(ast)) { + return { + validForNewPackages: false, + validForOldPackages: false, + spdx: true, + warnings: [genericWarning] + }; + } else { + return { + validForNewPackages: true, + validForOldPackages: true, + spdx: true + }; } +}; - //this will be registered as the first 'end' listener - //must call destroy next tick, to make sure we're after any - //stream piped from here. - //this is only a problem if end is not emitted synchronously. - //a nicer way to do this is to make sure this is the last listener for 'end' - stream.on('end', function () { - stream.readable = false - if(!stream.writable && stream.autoDestroy) - process.nextTick(function () { - stream.destroy() - }) - }) +/***/ }), - function _end () { - stream.writable = false - end.call(stream) - if(!stream.readable && stream.autoDestroy) - stream.destroy() - } +/***/ "../../node_modules/validate-npm-package-license/node_modules/spdx-correct/index.js": +/***/ (function(module, exports, __webpack_require__) { - stream.end = function (data) { - if(ended) return - ended = true - if(arguments.length) stream.write(data) - _end() // will emit or queue - return stream +var licenseIDs = __webpack_require__("../../node_modules/validate-npm-package-license/node_modules/spdx-license-ids/spdx-license-ids.json"); + +function valid(string) { + return licenseIDs.indexOf(string) > -1; +} + +// Common transpositions of license identifier acronyms +var transpositions = [ + ['APGL', 'AGPL'], + ['Gpl', 'GPL'], + ['GLP', 'GPL'], + ['APL', 'Apache'], + ['ISD', 'ISC'], + ['GLP', 'GPL'], + ['IST', 'ISC'], + ['Claude', 'Clause'], + [' or later', '+'], + [' International', ''], + ['GNU', 'GPL'], + ['GUN', 'GPL'], + ['+', ''], + ['GNU GPL', 'GPL'], + ['GNU/GPL', 'GPL'], + ['GNU GLP', 'GPL'], + ['GNU General Public License', 'GPL'], + ['Gnu public license', 'GPL'], + ['GNU Public License', 'GPL'], + ['GNU GENERAL PUBLIC LICENSE', 'GPL'], + ['MTI', 'MIT'], + ['Mozilla Public License', 'MPL'], + ['WTH', 'WTF'], + ['-License', ''] +]; + +var TRANSPOSED = 0; +var CORRECT = 1; + +// Simple corrections to nearly valid identifiers. +var transforms = [ + // e.g. 'mit' + function(argument) { + return argument.toUpperCase(); + }, + // e.g. 'MIT ' + function(argument) { + return argument.trim(); + }, + // e.g. 'M.I.T.' + function(argument) { + return argument.replace(/\./g, ''); + }, + // e.g. 'Apache- 2.0' + function(argument) { + return argument.replace(/\s+/g, ''); + }, + // e.g. 'CC BY 4.0'' + function(argument) { + return argument.replace(/\s+/g, '-'); + }, + // e.g. 'LGPLv2.1' + function(argument) { + return argument.replace('v', '-'); + }, + // e.g. 'Apache 2.0' + function(argument) { + return argument.replace(/,?\s*(\d)/, '-$1'); + }, + // e.g. 'GPL 2' + function(argument) { + return argument.replace(/,?\s*(\d)/, '-$1.0'); + }, + // e.g. 'Apache Version 2.0' + function(argument) { + return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2'); + }, + // e.g. 'Apache Version 2' + function(argument) { + return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0'); + }, + // e.g. 'ZLIB' + function(argument) { + return argument[0].toUpperCase() + argument.slice(1); + }, + // e.g. 'MPL/2.0' + function(argument) { + return argument.replace('/', '-'); + }, + // e.g. 'Apache 2' + function(argument) { + return argument + .replace(/\s*V\s*(\d)/, '-$1') + .replace(/(\d)$/, '$1.0'); + }, + // e.g. 'GPL-2.0-' + function(argument) { + return argument.slice(0, argument.length - 1); + }, + // e.g. 'GPL2' + function(argument) { + return argument.replace(/(\d)$/, '-$1.0'); + }, + // e.g. 'BSD 3' + function(argument) { + return argument.replace(/(-| )?(\d)$/, '-$2-Clause'); + }, + // e.g. 'BSD clause 3' + function(argument) { + return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause'); + }, + // e.g. 'BY-NC-4.0' + function(argument) { + return 'CC-' + argument; + }, + // e.g. 'BY-NC' + function(argument) { + return 'CC-' + argument + '-4.0'; + }, + // e.g. 'Attribution-NonCommercial' + function(argument) { + return argument + .replace('Attribution', 'BY') + .replace('NonCommercial', 'NC') + .replace('NoDerivatives', 'ND') + .replace(/ (\d)/, '-$1') + .replace(/ ?International/, ''); + }, + // e.g. 'Attribution-NonCommercial' + function(argument) { + return 'CC-' + + argument + .replace('Attribution', 'BY') + .replace('NonCommercial', 'NC') + .replace('NoDerivatives', 'ND') + .replace(/ (\d)/, '-$1') + .replace(/ ?International/, '') + + '-4.0'; } +]; - stream.destroy = function () { - if(destroyed) return - destroyed = true - ended = true - buffer.length = 0 - stream.writable = stream.readable = false - stream.emit('close') - return stream - } +// If all else fails, guess that strings containing certain substrings +// meant to identify certain licenses. +var lastResorts = [ + ['UNLI', 'Unlicense'], + ['WTF', 'WTFPL'], + ['2 CLAUSE', 'BSD-2-Clause'], + ['2-CLAUSE', 'BSD-2-Clause'], + ['3 CLAUSE', 'BSD-3-Clause'], + ['3-CLAUSE', 'BSD-3-Clause'], + ['AFFERO', 'AGPL-3.0'], + ['AGPL', 'AGPL-3.0'], + ['APACHE', 'Apache-2.0'], + ['ARTISTIC', 'Artistic-2.0'], + ['Affero', 'AGPL-3.0'], + ['BEER', 'Beerware'], + ['BOOST', 'BSL-1.0'], + ['BSD', 'BSD-2-Clause'], + ['ECLIPSE', 'EPL-1.0'], + ['FUCK', 'WTFPL'], + ['GNU', 'GPL-3.0'], + ['LGPL', 'LGPL-3.0'], + ['GPL', 'GPL-3.0'], + ['MIT', 'MIT'], + ['MPL', 'MPL-2.0'], + ['X11', 'X11'], + ['ZLIB', 'Zlib'] +]; - stream.pause = function () { - if(stream.paused) return - stream.paused = true - return stream - } +var SUBSTRING = 0; +var IDENTIFIER = 1; - stream.resume = function () { - if(stream.paused) { - stream.paused = false - stream.emit('resume') +var validTransformation = function(identifier) { + for (var i = 0; i < transforms.length; i++) { + var transformed = transforms[i](identifier); + if (transformed !== identifier && valid(transformed)) { + return transformed; } - drain() - //may have become paused again, - //as drain emits 'data'. - if(!stream.paused) - stream.emit('drain') - return stream - } - return stream -} - - - -/***/ }), - -/***/ "../../node_modules/to-regex-range/index.js": -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * to-regex-range - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ - - - -const isNumber = __webpack_require__("../../node_modules/to-regex-range/node_modules/is-number/index.js"); - -const toRegexRange = (min, max, options) => { - if (isNumber(min) === false) { - throw new TypeError('toRegexRange: expected the first argument to be a number'); } + return null; +}; - if (max === void 0 || min === max) { - return String(min); +var validLastResort = function(identifier) { + var upperCased = identifier.toUpperCase(); + for (var i = 0; i < lastResorts.length; i++) { + var lastResort = lastResorts[i]; + if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) { + return lastResort[IDENTIFIER]; + } } + return null; +}; - if (isNumber(max) === false) { - throw new TypeError('toRegexRange: expected the second argument to be a number.'); +var anyCorrection = function(identifier, check) { + for (var i = 0; i < transpositions.length; i++) { + var transposition = transpositions[i]; + var transposed = transposition[TRANSPOSED]; + if (identifier.indexOf(transposed) > -1) { + var corrected = identifier.replace( + transposed, + transposition[CORRECT] + ); + var checked = check(corrected); + if (checked !== null) { + return checked; + } + } } + return null; +}; - let opts = { relaxZeros: true, ...options }; - if (typeof opts.strictZeros === 'boolean') { - opts.relaxZeros = opts.strictZeros === false; +module.exports = function(identifier) { + identifier = identifier.replace(/\+$/, ''); + if (valid(identifier)) { + return identifier; } - - let relax = String(opts.relaxZeros); - let shorthand = String(opts.shorthand); - let capture = String(opts.capture); - let wrap = String(opts.wrap); - let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; - - if (toRegexRange.cache.hasOwnProperty(cacheKey)) { - return toRegexRange.cache[cacheKey].result; + var transformed = validTransformation(identifier); + if (transformed !== null) { + return transformed; } - - let a = Math.min(min, max); - let b = Math.max(min, max); - - if (Math.abs(a - b) === 1) { - let result = min + '|' + max; - if (opts.capture) { - return `(${result})`; - } - if (opts.wrap === false) { - return result; + transformed = anyCorrection(identifier, function(argument) { + if (valid(argument)) { + return argument; } - return `(?:${result})`; - } - - let isPadded = hasPadding(min) || hasPadding(max); - let state = { min, max, a, b }; - let positives = []; - let negatives = []; - - if (isPadded) { - state.isPadded = isPadded; - state.maxLen = String(state.max).length; - } - - if (a < 0) { - let newMin = b < 0 ? Math.abs(b) : 1; - negatives = splitToPatterns(newMin, Math.abs(a), state, opts); - a = state.a = 0; + return validTransformation(argument); + }); + if (transformed !== null) { + return transformed; } - - if (b >= 0) { - positives = splitToPatterns(a, b, state, opts); + transformed = validLastResort(identifier); + if (transformed !== null) { + return transformed; } - - state.negatives = negatives; - state.positives = positives; - state.result = collatePatterns(negatives, positives, opts); - - if (opts.capture === true) { - state.result = `(${state.result})`; - } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { - state.result = `(?:${state.result})`; + transformed = anyCorrection(identifier, validLastResort); + if (transformed !== null) { + return transformed; } - - toRegexRange.cache[cacheKey] = state; - return state.result; + return null; }; -function collatePatterns(neg, pos, options) { - let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; - let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; - let intersected = filterPatterns(neg, pos, '-?', true, options) || []; - let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); - return subpatterns.join('|'); -} - -function splitToRanges(min, max) { - let nines = 1; - let zeros = 1; - - let stop = countNines(min, nines); - let stops = new Set([max]); - - while (min <= stop && stop <= max) { - stops.add(stop); - nines += 1; - stop = countNines(min, nines); - } - - stop = countZeros(max + 1, zeros) - 1; - - while (min < stop && stop <= max) { - stops.add(stop); - zeros += 1; - stop = countZeros(max + 1, zeros) - 1; - } - - stops = [...stops]; - stops.sort(compare); - return stops; -} -/** - * Convert a range to a regex pattern - * @param {Number} `start` - * @param {Number} `stop` - * @return {String} - */ +/***/ }), -function rangeToPattern(start, stop, options) { - if (start === stop) { - return { pattern: start, count: [], digits: 0 }; - } +/***/ "../../node_modules/validate-npm-package-license/node_modules/spdx-expression-parse/index.js": +/***/ (function(module, exports, __webpack_require__) { - let zipped = zip(start, stop); - let digits = zipped.length; - let pattern = ''; - let count = 0; +var parser = __webpack_require__("../../node_modules/validate-npm-package-license/node_modules/spdx-expression-parse/parser.js").parser - for (let i = 0; i < digits; i++) { - let [startDigit, stopDigit] = zipped[i]; +module.exports = function (argument) { + return parser.parse(argument) +} - if (startDigit === stopDigit) { - pattern += startDigit; - } else if (startDigit !== '0' || stopDigit !== '9') { - pattern += toCharacterClass(startDigit, stopDigit, options); +/***/ }), - } else { - count++; - } - } +/***/ "../../node_modules/validate-npm-package-license/node_modules/spdx-expression-parse/parser.js": +/***/ (function(module, exports, __webpack_require__) { - if (count) { - pattern += options.shorthand === true ? '\\d' : '[0-9]'; - } +/* WEBPACK VAR INJECTION */(function(module) {/* parser generated by jison 0.4.17 */ +/* + Returns a Parser object of the following structure: - return { pattern, count: [count], digits }; -} + Parser: { + yy: {} + } -function splitToPatterns(min, max, tok, options) { - let ranges = splitToRanges(min, max); - let tokens = []; - let start = min; - let prev; + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), - for (let i = 0; i < ranges.length; i++) { - let max = ranges[i]; - let obj = rangeToPattern(String(start), String(max), options); - let zeros = ''; + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), - if (!tok.isPadded && prev && prev.pattern === obj.pattern) { - if (prev.count.length > 1) { - prev.count.pop(); - } + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, - prev.count.push(obj.count[0]); - prev.string = prev.pattern + toQuantifier(prev.count); - start = max + 1; - continue; + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, } + } - if (tok.isPadded) { - zeros = padZeros(max, tok, options); - } - obj.string = zeros + obj.pattern + toQuantifier(obj.count); - tokens.push(obj); - start = max + 1; - prev = obj; + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) } - return tokens; -} -function filterPatterns(arr, comparison, prefix, intersection, options) { - let result = []; + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var spdxparse = (function(){ +var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,6],$V2=[1,7],$V3=[1,4],$V4=[1,9],$V5=[1,10],$V6=[5,14,15,17],$V7=[5,12,14,15,17]; +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"start":3,"expression":4,"EOS":5,"simpleExpression":6,"LICENSE":7,"PLUS":8,"LICENSEREF":9,"DOCUMENTREF":10,"COLON":11,"WITH":12,"EXCEPTION":13,"AND":14,"OR":15,"OPEN":16,"CLOSE":17,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOS",7:"LICENSE",8:"PLUS",9:"LICENSEREF",10:"DOCUMENTREF",11:"COLON",12:"WITH",13:"EXCEPTION",14:"AND",15:"OR",16:"OPEN",17:"CLOSE"}, +productions_: [0,[3,2],[6,1],[6,2],[6,1],[6,3],[4,1],[4,3],[4,3],[4,3],[4,3]], +performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +/* this == yyval */ - for (let ele of arr) { - let { string } = ele; +var $0 = $$.length - 1; +switch (yystate) { +case 1: +return this.$ = $$[$0-1] +break; +case 2: case 4: case 5: +this.$ = {license: yytext} +break; +case 3: +this.$ = {license: $$[$0-1], plus: true} +break; +case 6: +this.$ = $$[$0] +break; +case 7: +this.$ = {exception: $$[$0]} +this.$.license = $$[$0-2].license +if ($$[$0-2].hasOwnProperty('plus')) { + this.$.plus = $$[$0-2].plus +} +break; +case 8: +this.$ = {conjunction: 'and', left: $$[$0-2], right: $$[$0]} +break; +case 9: +this.$ = {conjunction: 'or', left: $$[$0-2], right: $$[$0]} +break; +case 10: +this.$ = $$[$0-1] +break; +} +}, +table: [{3:1,4:2,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{1:[3]},{5:[1,8],14:$V4,15:$V5},o($V6,[2,6],{12:[1,11]}),{4:12,6:3,7:$V0,9:$V1,10:$V2,16:$V3},o($V7,[2,2],{8:[1,13]}),o($V7,[2,4]),{11:[1,14]},{1:[2,1]},{4:15,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{4:16,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{13:[1,17]},{14:$V4,15:$V5,17:[1,18]},o($V7,[2,3]),{9:[1,19]},o($V6,[2,8]),o([5,15,17],[2,9],{14:$V4}),o($V6,[2,7]),o($V6,[2,10]),o($V7,[2,5])], +defaultActions: {8:[2,1]}, +parseError: function parseError(str, hash) { + if (hash.recoverable) { + this.trace(str); + } else { + function _parseError (msg, hash) { + this.message = msg; + this.hash = hash; + } + _parseError.prototype = Error; - // only push if _both_ are negative... - if (!intersection && !contains(comparison, 'string', string)) { - result.push(prefix + string); + throw new _parseError(str, hash); } - - // or _both_ are positive - if (intersection && contains(comparison, 'string', string)) { - result.push(prefix + string); +}, +parse: function parse(input) { + var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + var args = lstack.slice.call(arguments, 1); + var lexer = Object.create(this.lexer); + var sharedState = { yy: {} }; + for (var k in this.yy) { + if (Object.prototype.hasOwnProperty.call(this.yy, k)) { + sharedState.yy[k] = this.yy[k]; + } + } + lexer.setInput(input, sharedState.yy); + sharedState.yy.lexer = lexer; + sharedState.yy.parser = this; + if (typeof lexer.yylloc == 'undefined') { + lexer.yylloc = {}; + } + var yyloc = lexer.yylloc; + lstack.push(yyloc); + var ranges = lexer.options && lexer.options.ranges; + if (typeof sharedState.yy.parseError === 'function') { + this.parseError = sharedState.yy.parseError; + } else { + this.parseError = Object.getPrototypeOf(this).parseError; + } + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + _token_stack: + var lex = function () { + var token; + token = lexer.lex() || EOF; + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + }; + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == 'undefined') { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === 'undefined' || !action.length || !action[0]) { + var errStr = ''; + expected = []; + for (p in table[state]) { + if (this.terminals_[p] && p > TERROR) { + expected.push('\'' + this.terminals_[p] + '\''); + } + } + if (lexer.showPosition) { + errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; + } else { + errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); + } + this.parseError(errStr, { + text: lexer.match, + token: this.terminals_[symbol] || symbol, + line: lexer.yylineno, + loc: yyloc, + expected: expected + }); + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(lexer.yytext); + lstack.push(lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = lexer.yyleng; + yytext = lexer.yytext; + yylineno = lexer.yylineno; + yyloc = lexer.yylloc; + if (recovering > 0) { + recovering--; + } + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }; + if (ranges) { + yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]; + } + r = this.performAction.apply(yyval, [ + yytext, + yyleng, + yylineno, + sharedState.yy, + action[1], + vstack, + lstack + ].concat(args)); + if (typeof r !== 'undefined') { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } } - } - return result; -} - -/** - * Zip strings - */ - -function zip(a, b) { - let arr = []; - for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); - return arr; -} - -function compare(a, b) { - return a > b ? 1 : b > a ? -1 : 0; -} - -function contains(arr, key, val) { - return arr.some(ele => ele[key] === val); -} - -function countNines(min, len) { - return Number(String(min).slice(0, -len) + '9'.repeat(len)); -} - -function countZeros(integer, zeros) { - return integer - (integer % Math.pow(10, zeros)); -} - -function toQuantifier(digits) { - let [start = 0, stop = ''] = digits; - if (stop || start > 1) { - return `{${start + (stop ? ',' + stop : '')}}`; - } - return ''; -} - -function toCharacterClass(a, b, options) { - return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; -} - -function hasPadding(str) { - return /^-?(0+)\d/.test(str); -} - -function padZeros(value, tok, options) { - if (!tok.isPadded) { - return value; - } - - let diff = Math.abs(tok.maxLen - String(value).length); - let relax = options.relaxZeros !== false; + return true; +}}; +/* generated by jison-lex 0.3.4 */ +var lexer = (function(){ +var lexer = ({ - switch (diff) { - case 0: - return ''; - case 1: - return relax ? '0?' : '0'; - case 2: - return relax ? '0{0,2}' : '00'; - default: { - return relax ? `0{0,${diff}}` : `0{${diff}}`; - } - } -} +EOF:1, -/** - * Cache - */ +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, -toRegexRange.cache = {}; -toRegexRange.clearCache = () => (toRegexRange.cache = {}); +// resets the lexer, sets new input +setInput:function (input, yy) { + this.yy = yy || this.yy || {}; + this._input = input; + this._more = this._backtrack = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { + first_line: 1, + first_column: 0, + last_line: 1, + last_column: 0 + }; + if (this.options.ranges) { + this.yylloc.range = [0,0]; + } + this.offset = 0; + return this; + }, -/** - * Expose `toRegexRange` - */ +// consumes and returns one char from the input +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) { + this.yylloc.range[1]++; + } -module.exports = toRegexRange; + this._input = this._input.slice(1); + return ch; + }, +// unshifts one char (or a string) into the input +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); -/***/ }), + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); -/***/ "../../node_modules/to-regex-range/node_modules/is-number/index.js": -/***/ (function(module, exports, __webpack_require__) { + if (lines.length - 1) { + this.yylineno -= lines.length - 1; + } + var r = this.yylloc.range; -"use strict"; -/*! - * is-number - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ + this.yylloc = { + first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + + oldLines[oldLines.length - lines.length].length - lines[0].length : + this.yylloc.first_column - len + }; + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + this.yyleng = this.yytext.length; + return this; + }, +// When called from action, caches matched text and appends it on next action +more:function () { + this._more = true; + return this; + }, -module.exports = function(num) { - if (typeof num === 'number') { - return num - num === 0; - } - if (typeof num === 'string' && num.trim() !== '') { - return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); - } - return false; -}; +// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. +reject:function () { + if (this.options.backtrack_lexer) { + this._backtrack = true; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + } + return this; + }, -/***/ }), +// retain first n characters of the match +less:function (n) { + this.unput(this.match.slice(n)); + }, -/***/ "../../node_modules/validate-npm-package-license/index.js": -/***/ (function(module, exports, __webpack_require__) { +// displays already matched input, i.e. for error messages +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, -var parse = __webpack_require__("../../node_modules/spdx-expression-parse/index.js"); -var correct = __webpack_require__("../../node_modules/validate-npm-package-license/node_modules/spdx-correct/index.js"); +// displays upcoming input, i.e. for error messages +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, -var genericWarning = ( - 'license should be ' + - 'a valid SPDX license expression (without "LicenseRef"), ' + - '"UNLICENSED", or ' + - '"SEE LICENSE IN "' -); +// displays the character position where the lexing error occurred, i.e. for error messages +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, -var fileReferenceRE = /^SEE LICEN[CS]E IN (.+)$/; +// test the lexed token: return FALSE when not a match, otherwise return token +test_match:function (match, indexed_rule) { + var token, + lines, + backup; -function startsWith(prefix, string) { - return string.slice(0, prefix.length) === prefix; -} + if (this.options.backtrack_lexer) { + // save context + backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }; + if (this.options.ranges) { + backup.yylloc.range = this.yylloc.range.slice(0); + } + } -function usesLicenseRef(ast) { - if (ast.hasOwnProperty('license')) { - var license = ast.license; - return ( - startsWith('LicenseRef', license) || - startsWith('DocumentRef', license) - ); - } else { - return ( - usesLicenseRef(ast.left) || - usesLicenseRef(ast.right) - ); - } -} + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno += lines.length; + } + this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? + lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : + this.yylloc.last_column + match[0].length + }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._backtrack = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) { + this.done = false; + } + if (token) { + return token; + } else if (this._backtrack) { + // recover context + for (var k in backup) { + this[k] = backup[k]; + } + return false; // rule action called reject() implying the next rule should be tested instead. + } + return false; + }, -module.exports = function(argument) { - var ast; +// return next match in input +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) { + this.done = true; + } - try { - ast = parse(argument); - } catch (e) { - var match - if ( - argument === 'UNLICENSED' || - argument === 'UNLICENCED' - ) { - return { - validForOldPackages: true, - validForNewPackages: true, - unlicensed: true - }; - } else if (match = fileReferenceRE.exec(argument)) { - return { - validForOldPackages: true, - validForNewPackages: true, - inFile: match[1] - }; - } else { - var result = { - validForOldPackages: false, - validForNewPackages: false, - warnings: [genericWarning] - }; - var corrected = correct(argument); - if (corrected) { - result.warnings.push( - 'license is similar to the valid expression "' + corrected + '"' - ); - } - return result; - } - } + var token, + match, + tempMatch, + index; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (this.options.backtrack_lexer) { + token = this.test_match(tempMatch, rules[i]); + if (token !== false) { + return token; + } else if (this._backtrack) { + match = false; + continue; // rule action called reject() implying a rule MISmatch. + } else { + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + } else if (!this.options.flex) { + break; + } + } + } + if (match) { + token = this.test_match(match, rules[index]); + if (token !== false) { + return token; + } + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + } + }, - if (usesLicenseRef(ast)) { - return { - validForNewPackages: false, - validForOldPackages: false, - spdx: true, - warnings: [genericWarning] - }; - } else { - return { - validForNewPackages: true, - validForOldPackages: true, - spdx: true - }; - } -}; +// return next match that has a token +lex:function lex() { + var r = this.next(); + if (r) { + return r; + } else { + return this.lex(); + } + }, + +// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +// pop the previously active lexer condition state off the condition stack +popState:function popState() { + var n = this.conditionStack.length - 1; + if (n > 0) { + return this.conditionStack.pop(); + } else { + return this.conditionStack[0]; + } + }, -/***/ }), +// produce the lexer rule set which is active for the currently active lexer condition state +_currentRules:function _currentRules() { + if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + } else { + return this.conditions["INITIAL"].rules; + } + }, -/***/ "../../node_modules/validate-npm-package-license/node_modules/spdx-correct/index.js": -/***/ (function(module, exports, __webpack_require__) { +// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +topState:function topState(n) { + n = this.conditionStack.length - 1 - Math.abs(n || 0); + if (n >= 0) { + return this.conditionStack[n]; + } else { + return "INITIAL"; + } + }, -var licenseIDs = __webpack_require__("../../node_modules/spdx-license-ids/spdx-license-ids.json"); +// alias for begin(condition) +pushState:function pushState(condition) { + this.begin(condition); + }, -function valid(string) { - return licenseIDs.indexOf(string) > -1; +// return the number of states currently on the stack +stateStackSize:function stateStackSize() { + return this.conditionStack.length; + }, +options: {}, +performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:return 5 +break; +case 1:/* skip whitespace */ +break; +case 2:return 8 +break; +case 3:return 16 +break; +case 4:return 17 +break; +case 5:return 11 +break; +case 6:return 10 +break; +case 7:return 9 +break; +case 8:return 14 +break; +case 9:return 15 +break; +case 10:return 12 +break; +case 11:return 7 +break; +case 12:return 7 +break; +case 13:return 7 +break; +case 14:return 7 +break; +case 15:return 7 +break; +case 16:return 7 +break; +case 17:return 7 +break; +case 18:return 7 +break; +case 19:return 7 +break; +case 20:return 7 +break; +case 21:return 7 +break; +case 22:return 7 +break; +case 23:return 7 +break; +case 24:return 13 +break; +case 25:return 13 +break; +case 26:return 13 +break; +case 27:return 13 +break; +case 28:return 13 +break; +case 29:return 13 +break; +case 30:return 13 +break; +case 31:return 13 +break; +case 32:return 7 +break; +case 33:return 13 +break; +case 34:return 7 +break; +case 35:return 13 +break; +case 36:return 7 +break; +case 37:return 13 +break; +case 38:return 13 +break; +case 39:return 7 +break; +case 40:return 13 +break; +case 41:return 13 +break; +case 42:return 13 +break; +case 43:return 13 +break; +case 44:return 13 +break; +case 45:return 7 +break; +case 46:return 13 +break; +case 47:return 7 +break; +case 48:return 7 +break; +case 49:return 7 +break; +case 50:return 7 +break; +case 51:return 7 +break; +case 52:return 7 +break; +case 53:return 7 +break; +case 54:return 7 +break; +case 55:return 7 +break; +case 56:return 7 +break; +case 57:return 7 +break; +case 58:return 7 +break; +case 59:return 7 +break; +case 60:return 7 +break; +case 61:return 7 +break; +case 62:return 7 +break; +case 63:return 13 +break; +case 64:return 7 +break; +case 65:return 7 +break; +case 66:return 13 +break; +case 67:return 7 +break; +case 68:return 7 +break; +case 69:return 7 +break; +case 70:return 7 +break; +case 71:return 7 +break; +case 72:return 7 +break; +case 73:return 13 +break; +case 74:return 7 +break; +case 75:return 13 +break; +case 76:return 7 +break; +case 77:return 7 +break; +case 78:return 7 +break; +case 79:return 7 +break; +case 80:return 7 +break; +case 81:return 7 +break; +case 82:return 7 +break; +case 83:return 7 +break; +case 84:return 7 +break; +case 85:return 7 +break; +case 86:return 7 +break; +case 87:return 7 +break; +case 88:return 7 +break; +case 89:return 7 +break; +case 90:return 7 +break; +case 91:return 7 +break; +case 92:return 7 +break; +case 93:return 7 +break; +case 94:return 7 +break; +case 95:return 7 +break; +case 96:return 7 +break; +case 97:return 7 +break; +case 98:return 7 +break; +case 99:return 7 +break; +case 100:return 7 +break; +case 101:return 7 +break; +case 102:return 7 +break; +case 103:return 7 +break; +case 104:return 7 +break; +case 105:return 7 +break; +case 106:return 7 +break; +case 107:return 7 +break; +case 108:return 7 +break; +case 109:return 7 +break; +case 110:return 7 +break; +case 111:return 7 +break; +case 112:return 7 +break; +case 113:return 7 +break; +case 114:return 7 +break; +case 115:return 7 +break; +case 116:return 7 +break; +case 117:return 7 +break; +case 118:return 7 +break; +case 119:return 7 +break; +case 120:return 7 +break; +case 121:return 7 +break; +case 122:return 7 +break; +case 123:return 7 +break; +case 124:return 7 +break; +case 125:return 7 +break; +case 126:return 7 +break; +case 127:return 7 +break; +case 128:return 7 +break; +case 129:return 7 +break; +case 130:return 7 +break; +case 131:return 7 +break; +case 132:return 7 +break; +case 133:return 7 +break; +case 134:return 7 +break; +case 135:return 7 +break; +case 136:return 7 +break; +case 137:return 7 +break; +case 138:return 7 +break; +case 139:return 7 +break; +case 140:return 7 +break; +case 141:return 7 +break; +case 142:return 7 +break; +case 143:return 7 +break; +case 144:return 7 +break; +case 145:return 7 +break; +case 146:return 7 +break; +case 147:return 7 +break; +case 148:return 7 +break; +case 149:return 7 +break; +case 150:return 7 +break; +case 151:return 7 +break; +case 152:return 7 +break; +case 153:return 7 +break; +case 154:return 7 +break; +case 155:return 7 +break; +case 156:return 7 +break; +case 157:return 7 +break; +case 158:return 7 +break; +case 159:return 7 +break; +case 160:return 7 +break; +case 161:return 7 +break; +case 162:return 7 +break; +case 163:return 7 +break; +case 164:return 7 +break; +case 165:return 7 +break; +case 166:return 7 +break; +case 167:return 7 +break; +case 168:return 7 +break; +case 169:return 7 +break; +case 170:return 7 +break; +case 171:return 7 +break; +case 172:return 7 +break; +case 173:return 7 +break; +case 174:return 7 +break; +case 175:return 7 +break; +case 176:return 7 +break; +case 177:return 7 +break; +case 178:return 7 +break; +case 179:return 7 +break; +case 180:return 7 +break; +case 181:return 7 +break; +case 182:return 7 +break; +case 183:return 7 +break; +case 184:return 7 +break; +case 185:return 7 +break; +case 186:return 7 +break; +case 187:return 7 +break; +case 188:return 7 +break; +case 189:return 7 +break; +case 190:return 7 +break; +case 191:return 7 +break; +case 192:return 7 +break; +case 193:return 7 +break; +case 194:return 7 +break; +case 195:return 7 +break; +case 196:return 7 +break; +case 197:return 7 +break; +case 198:return 7 +break; +case 199:return 7 +break; +case 200:return 7 +break; +case 201:return 7 +break; +case 202:return 7 +break; +case 203:return 7 +break; +case 204:return 7 +break; +case 205:return 7 +break; +case 206:return 7 +break; +case 207:return 7 +break; +case 208:return 7 +break; +case 209:return 7 +break; +case 210:return 7 +break; +case 211:return 7 +break; +case 212:return 7 +break; +case 213:return 7 +break; +case 214:return 7 +break; +case 215:return 7 +break; +case 216:return 7 +break; +case 217:return 7 +break; +case 218:return 7 +break; +case 219:return 7 +break; +case 220:return 7 +break; +case 221:return 7 +break; +case 222:return 7 +break; +case 223:return 7 +break; +case 224:return 7 +break; +case 225:return 7 +break; +case 226:return 7 +break; +case 227:return 7 +break; +case 228:return 7 +break; +case 229:return 7 +break; +case 230:return 7 +break; +case 231:return 7 +break; +case 232:return 7 +break; +case 233:return 7 +break; +case 234:return 7 +break; +case 235:return 7 +break; +case 236:return 7 +break; +case 237:return 7 +break; +case 238:return 7 +break; +case 239:return 7 +break; +case 240:return 7 +break; +case 241:return 7 +break; +case 242:return 7 +break; +case 243:return 7 +break; +case 244:return 7 +break; +case 245:return 7 +break; +case 246:return 7 +break; +case 247:return 7 +break; +case 248:return 7 +break; +case 249:return 7 +break; +case 250:return 7 +break; +case 251:return 7 +break; +case 252:return 7 +break; +case 253:return 7 +break; +case 254:return 7 +break; +case 255:return 7 +break; +case 256:return 7 +break; +case 257:return 7 +break; +case 258:return 7 +break; +case 259:return 7 +break; +case 260:return 7 +break; +case 261:return 7 +break; +case 262:return 7 +break; +case 263:return 7 +break; +case 264:return 7 +break; +case 265:return 7 +break; +case 266:return 7 +break; +case 267:return 7 +break; +case 268:return 7 +break; +case 269:return 7 +break; +case 270:return 7 +break; +case 271:return 7 +break; +case 272:return 7 +break; +case 273:return 7 +break; +case 274:return 7 +break; +case 275:return 7 +break; +case 276:return 7 +break; +case 277:return 7 +break; +case 278:return 7 +break; +case 279:return 7 +break; +case 280:return 7 +break; +case 281:return 7 +break; +case 282:return 7 +break; +case 283:return 7 +break; +case 284:return 7 +break; +case 285:return 7 +break; +case 286:return 7 +break; +case 287:return 7 +break; +case 288:return 7 +break; +case 289:return 7 +break; +case 290:return 7 +break; +case 291:return 7 +break; +case 292:return 7 +break; +case 293:return 7 +break; +case 294:return 7 +break; +case 295:return 7 +break; +case 296:return 7 +break; +case 297:return 7 +break; +case 298:return 7 +break; +case 299:return 7 +break; +case 300:return 7 +break; +case 301:return 7 +break; +case 302:return 7 +break; +case 303:return 7 +break; +case 304:return 7 +break; +case 305:return 7 +break; +case 306:return 7 +break; +case 307:return 7 +break; +case 308:return 7 +break; +case 309:return 7 +break; +case 310:return 7 +break; +case 311:return 7 +break; +case 312:return 7 +break; +case 313:return 7 +break; +case 314:return 7 +break; +case 315:return 7 +break; +case 316:return 7 +break; +case 317:return 7 +break; +case 318:return 7 +break; +case 319:return 7 +break; +case 320:return 7 +break; +case 321:return 7 +break; +case 322:return 7 +break; +case 323:return 7 +break; +case 324:return 7 +break; +case 325:return 7 +break; +case 326:return 7 +break; +case 327:return 7 +break; +case 328:return 7 +break; +case 329:return 7 +break; +case 330:return 7 +break; +case 331:return 7 +break; +case 332:return 7 +break; +case 333:return 7 +break; +case 334:return 7 +break; +case 335:return 7 +break; +case 336:return 7 +break; +case 337:return 7 +break; +case 338:return 7 +break; +case 339:return 7 +break; +case 340:return 7 +break; +case 341:return 7 +break; +case 342:return 7 +break; +case 343:return 7 +break; +case 344:return 7 +break; +case 345:return 7 +break; +case 346:return 7 +break; +case 347:return 7 +break; +case 348:return 7 +break; +case 349:return 7 +break; +case 350:return 7 +break; +case 351:return 7 +break; +case 352:return 7 +break; +case 353:return 7 +break; +case 354:return 7 +break; +case 355:return 7 +break; +case 356:return 7 +break; +case 357:return 7 +break; +case 358:return 7 +break; +case 359:return 7 +break; +case 360:return 7 +break; +case 361:return 7 +break; +case 362:return 7 +break; +case 363:return 7 +break; +case 364:return 7 +break; } +}, +rules: [/^(?:$)/,/^(?:\s+)/,/^(?:\+)/,/^(?:\()/,/^(?:\))/,/^(?::)/,/^(?:DocumentRef-([0-9A-Za-z-+.]+))/,/^(?:LicenseRef-([0-9A-Za-z-+.]+))/,/^(?:AND)/,/^(?:OR)/,/^(?:WITH)/,/^(?:BSD-3-Clause-No-Nuclear-License-2014)/,/^(?:BSD-3-Clause-No-Nuclear-Warranty)/,/^(?:GPL-2\.0-with-classpath-exception)/,/^(?:GPL-3\.0-with-autoconf-exception)/,/^(?:GPL-2\.0-with-autoconf-exception)/,/^(?:BSD-3-Clause-No-Nuclear-License)/,/^(?:MPL-2\.0-no-copyleft-exception)/,/^(?:GPL-2\.0-with-bison-exception)/,/^(?:GPL-2\.0-with-font-exception)/,/^(?:GPL-2\.0-with-GCC-exception)/,/^(?:CNRI-Python-GPL-Compatible)/,/^(?:GPL-3\.0-with-GCC-exception)/,/^(?:BSD-3-Clause-Attribution)/,/^(?:Classpath-exception-2\.0)/,/^(?:WxWindows-exception-3\.1)/,/^(?:freertos-exception-2\.0)/,/^(?:Autoconf-exception-3\.0)/,/^(?:i2p-gpl-java-exception)/,/^(?:gnu-javamail-exception)/,/^(?:Nokia-Qt-exception-1\.1)/,/^(?:Autoconf-exception-2\.0)/,/^(?:BSD-2-Clause-FreeBSD)/,/^(?:u-boot-exception-2\.0)/,/^(?:zlib-acknowledgement)/,/^(?:Bison-exception-2\.2)/,/^(?:BSD-2-Clause-NetBSD)/,/^(?:CLISP-exception-2\.0)/,/^(?:eCos-exception-2\.0)/,/^(?:BSD-3-Clause-Clear)/,/^(?:Font-exception-2\.0)/,/^(?:FLTK-exception-2\.0)/,/^(?:GCC-exception-2\.0)/,/^(?:Qwt-exception-1\.0)/,/^(?:Libtool-exception)/,/^(?:BSD-3-Clause-LBNL)/,/^(?:GCC-exception-3\.1)/,/^(?:Artistic-1\.0-Perl)/,/^(?:Artistic-1\.0-cl8)/,/^(?:CC-BY-NC-SA-2\.5)/,/^(?:MIT-advertising)/,/^(?:BSD-Source-Code)/,/^(?:CC-BY-NC-SA-4\.0)/,/^(?:LiLiQ-Rplus-1\.1)/,/^(?:CC-BY-NC-SA-3\.0)/,/^(?:BSD-4-Clause-UC)/,/^(?:CC-BY-NC-SA-2\.0)/,/^(?:CC-BY-NC-SA-1\.0)/,/^(?:CC-BY-NC-ND-4\.0)/,/^(?:CC-BY-NC-ND-3\.0)/,/^(?:CC-BY-NC-ND-2\.5)/,/^(?:CC-BY-NC-ND-2\.0)/,/^(?:CC-BY-NC-ND-1\.0)/,/^(?:LZMA-exception)/,/^(?:BitTorrent-1\.1)/,/^(?:CrystalStacker)/,/^(?:FLTK-exception)/,/^(?:SugarCRM-1\.1\.3)/,/^(?:BSD-Protection)/,/^(?:BitTorrent-1\.0)/,/^(?:HaskellReport)/,/^(?:Interbase-1\.0)/,/^(?:StandardML-NJ)/,/^(?:mif-exception)/,/^(?:Frameworx-1\.0)/,/^(?:389-exception)/,/^(?:CC-BY-NC-2\.0)/,/^(?:CC-BY-NC-2\.5)/,/^(?:CC-BY-NC-3\.0)/,/^(?:CC-BY-NC-4\.0)/,/^(?:W3C-19980720)/,/^(?:CC-BY-SA-1\.0)/,/^(?:CC-BY-SA-2\.0)/,/^(?:CC-BY-SA-2\.5)/,/^(?:CC-BY-ND-2\.0)/,/^(?:CC-BY-SA-4\.0)/,/^(?:CC-BY-SA-3\.0)/,/^(?:Artistic-1\.0)/,/^(?:Artistic-2\.0)/,/^(?:CC-BY-ND-2\.5)/,/^(?:CC-BY-ND-3\.0)/,/^(?:CC-BY-ND-4\.0)/,/^(?:CC-BY-ND-1\.0)/,/^(?:BSD-4-Clause)/,/^(?:BSD-3-Clause)/,/^(?:BSD-2-Clause)/,/^(?:CC-BY-NC-1\.0)/,/^(?:bzip2-1\.0\.6)/,/^(?:Unicode-TOU)/,/^(?:CNRI-Jython)/,/^(?:ImageMagick)/,/^(?:Adobe-Glyph)/,/^(?:CUA-OPL-1\.0)/,/^(?:OLDAP-2\.2\.2)/,/^(?:LiLiQ-R-1\.1)/,/^(?:bzip2-1\.0\.5)/,/^(?:LiLiQ-P-1\.1)/,/^(?:OLDAP-2\.0\.1)/,/^(?:OLDAP-2\.2\.1)/,/^(?:CNRI-Python)/,/^(?:XFree86-1\.1)/,/^(?:OSET-PL-2\.1)/,/^(?:Apache-2\.0)/,/^(?:Watcom-1\.0)/,/^(?:PostgreSQL)/,/^(?:Python-2\.0)/,/^(?:RHeCos-1\.1)/,/^(?:EUDatagrid)/,/^(?:Spencer-99)/,/^(?:Intel-ACPI)/,/^(?:CECILL-1\.0)/,/^(?:CECILL-1\.1)/,/^(?:JasPer-2\.0)/,/^(?:CECILL-2\.0)/,/^(?:CECILL-2\.1)/,/^(?:gSOAP-1\.3b)/,/^(?:Spencer-94)/,/^(?:Apache-1\.1)/,/^(?:Spencer-86)/,/^(?:Apache-1\.0)/,/^(?:ClArtistic)/,/^(?:TORQUE-1\.1)/,/^(?:CATOSL-1\.1)/,/^(?:Adobe-2006)/,/^(?:Zimbra-1\.4)/,/^(?:Zimbra-1\.3)/,/^(?:Condor-1\.1)/,/^(?:CC-BY-3\.0)/,/^(?:CC-BY-2\.5)/,/^(?:OLDAP-2\.4)/,/^(?:SGI-B-1\.1)/,/^(?:SISSL-1\.2)/,/^(?:SGI-B-1\.0)/,/^(?:OLDAP-2\.3)/,/^(?:CC-BY-4\.0)/,/^(?:Crossword)/,/^(?:SimPL-2\.0)/,/^(?:OLDAP-2\.2)/,/^(?:OLDAP-2\.1)/,/^(?:ErlPL-1\.1)/,/^(?:LPPL-1\.3a)/,/^(?:LPPL-1\.3c)/,/^(?:OLDAP-2\.0)/,/^(?:Leptonica)/,/^(?:CPOL-1\.02)/,/^(?:OLDAP-1\.4)/,/^(?:OLDAP-1\.3)/,/^(?:CC-BY-2\.0)/,/^(?:Unlicense)/,/^(?:OLDAP-2\.8)/,/^(?:OLDAP-1\.2)/,/^(?:MakeIndex)/,/^(?:OLDAP-2\.7)/,/^(?:OLDAP-1\.1)/,/^(?:Sleepycat)/,/^(?:D-FSL-1\.0)/,/^(?:CC-BY-1\.0)/,/^(?:OLDAP-2\.6)/,/^(?:WXwindows)/,/^(?:NPOSL-3\.0)/,/^(?:FreeImage)/,/^(?:SGI-B-2\.0)/,/^(?:OLDAP-2\.5)/,/^(?:Beerware)/,/^(?:Newsletr)/,/^(?:NBPL-1\.0)/,/^(?:NASA-1\.3)/,/^(?:NLOD-1\.0)/,/^(?:AGPL-1\.0)/,/^(?:OCLC-2\.0)/,/^(?:ODbL-1\.0)/,/^(?:PDDL-1\.0)/,/^(?:Motosoto)/,/^(?:Afmparse)/,/^(?:ANTLR-PD)/,/^(?:LPL-1\.02)/,/^(?:Abstyles)/,/^(?:eCos-2\.0)/,/^(?:APSL-1\.0)/,/^(?:LPPL-1\.2)/,/^(?:LPPL-1\.1)/,/^(?:LPPL-1\.0)/,/^(?:APSL-1\.1)/,/^(?:APSL-2\.0)/,/^(?:Info-ZIP)/,/^(?:Zend-2\.0)/,/^(?:IBM-pibs)/,/^(?:LGPL-2\.0)/,/^(?:LGPL-3\.0)/,/^(?:LGPL-2\.1)/,/^(?:GFDL-1\.3)/,/^(?:PHP-3\.01)/,/^(?:GFDL-1\.2)/,/^(?:GFDL-1\.1)/,/^(?:AGPL-3\.0)/,/^(?:Giftware)/,/^(?:EUPL-1\.1)/,/^(?:RPSL-1\.0)/,/^(?:EUPL-1\.0)/,/^(?:MIT-enna)/,/^(?:CECILL-B)/,/^(?:diffmark)/,/^(?:CECILL-C)/,/^(?:CDDL-1\.0)/,/^(?:Sendmail)/,/^(?:CDDL-1\.1)/,/^(?:CPAL-1\.0)/,/^(?:APSL-1\.2)/,/^(?:NPL-1\.1)/,/^(?:AFL-1\.2)/,/^(?:Caldera)/,/^(?:AFL-2\.0)/,/^(?:FSFULLR)/,/^(?:AFL-2\.1)/,/^(?:VSL-1\.0)/,/^(?:VOSTROM)/,/^(?:UPL-1\.0)/,/^(?:Dotseqn)/,/^(?:CPL-1\.0)/,/^(?:dvipdfm)/,/^(?:EPL-1\.0)/,/^(?:OCCT-PL)/,/^(?:ECL-1\.0)/,/^(?:Latex2e)/,/^(?:ECL-2\.0)/,/^(?:GPL-1\.0)/,/^(?:GPL-2\.0)/,/^(?:GPL-3\.0)/,/^(?:AFL-3\.0)/,/^(?:LAL-1\.2)/,/^(?:LAL-1\.3)/,/^(?:EFL-1\.0)/,/^(?:EFL-2\.0)/,/^(?:gnuplot)/,/^(?:Aladdin)/,/^(?:LPL-1\.0)/,/^(?:libtiff)/,/^(?:Entessa)/,/^(?:AMDPLPA)/,/^(?:IPL-1\.0)/,/^(?:OPL-1\.0)/,/^(?:OSL-1\.0)/,/^(?:OSL-1\.1)/,/^(?:OSL-2\.0)/,/^(?:OSL-2\.1)/,/^(?:OSL-3\.0)/,/^(?:OpenSSL)/,/^(?:ZPL-2\.1)/,/^(?:PHP-3\.0)/,/^(?:ZPL-2\.0)/,/^(?:ZPL-1\.1)/,/^(?:CC0-1\.0)/,/^(?:SPL-1\.0)/,/^(?:psutils)/,/^(?:MPL-1\.0)/,/^(?:QPL-1\.0)/,/^(?:MPL-1\.1)/,/^(?:MPL-2\.0)/,/^(?:APL-1\.0)/,/^(?:RPL-1\.1)/,/^(?:RPL-1\.5)/,/^(?:MIT-CMU)/,/^(?:Multics)/,/^(?:Eurosym)/,/^(?:BSL-1\.0)/,/^(?:MIT-feh)/,/^(?:Saxpath)/,/^(?:Borceux)/,/^(?:OFL-1\.1)/,/^(?:OFL-1\.0)/,/^(?:AFL-1\.1)/,/^(?:YPL-1\.1)/,/^(?:YPL-1\.0)/,/^(?:NPL-1\.0)/,/^(?:iMatix)/,/^(?:mpich2)/,/^(?:APAFML)/,/^(?:Bahyph)/,/^(?:RSA-MD)/,/^(?:psfrag)/,/^(?:Plexus)/,/^(?:eGenix)/,/^(?:Glulxe)/,/^(?:SAX-PD)/,/^(?:Imlib2)/,/^(?:Wsuipa)/,/^(?:LGPLLR)/,/^(?:Libpng)/,/^(?:xinetd)/,/^(?:MITNFA)/,/^(?:NetCDF)/,/^(?:Naumen)/,/^(?:SMPPL)/,/^(?:Nunit)/,/^(?:FSFUL)/,/^(?:GL2PS)/,/^(?:SMLNJ)/,/^(?:Rdisc)/,/^(?:Noweb)/,/^(?:Nokia)/,/^(?:SISSL)/,/^(?:Qhull)/,/^(?:Intel)/,/^(?:Glide)/,/^(?:Xerox)/,/^(?:AMPAS)/,/^(?:WTFPL)/,/^(?:MS-PL)/,/^(?:XSkat)/,/^(?:MS-RL)/,/^(?:MirOS)/,/^(?:RSCPL)/,/^(?:TMate)/,/^(?:OGTSL)/,/^(?:FSFAP)/,/^(?:NCSA)/,/^(?:Zlib)/,/^(?:SCEA)/,/^(?:SNIA)/,/^(?:NGPL)/,/^(?:NOSL)/,/^(?:ADSL)/,/^(?:MTLL)/,/^(?:NLPL)/,/^(?:Ruby)/,/^(?:JSON)/,/^(?:Barr)/,/^(?:0BSD)/,/^(?:Xnet)/,/^(?:Cube)/,/^(?:curl)/,/^(?:DSDP)/,/^(?:Fair)/,/^(?:HPND)/,/^(?:TOSL)/,/^(?:IJG)/,/^(?:SWL)/,/^(?:Vim)/,/^(?:FTL)/,/^(?:ICU)/,/^(?:OML)/,/^(?:NRL)/,/^(?:DOC)/,/^(?:TCL)/,/^(?:W3C)/,/^(?:NTP)/,/^(?:IPA)/,/^(?:ISC)/,/^(?:X11)/,/^(?:AAL)/,/^(?:AML)/,/^(?:xpp)/,/^(?:Zed)/,/^(?:MIT)/,/^(?:Mup)/], +conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364],"inclusive":true}} +}); +return lexer; +})(); +parser.lexer = lexer; +function Parser () { + this.yy = {}; +} +Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})(); -// Common transpositions of license identifier acronyms -var transpositions = [ - ['APGL', 'AGPL'], - ['Gpl', 'GPL'], - ['GLP', 'GPL'], - ['APL', 'Apache'], - ['ISD', 'ISC'], - ['GLP', 'GPL'], - ['IST', 'ISC'], - ['Claude', 'Clause'], - [' or later', '+'], - [' International', ''], - ['GNU', 'GPL'], - ['GUN', 'GPL'], - ['+', ''], - ['GNU GPL', 'GPL'], - ['GNU/GPL', 'GPL'], - ['GNU GLP', 'GPL'], - ['GNU General Public License', 'GPL'], - ['Gnu public license', 'GPL'], - ['GNU Public License', 'GPL'], - ['GNU GENERAL PUBLIC LICENSE', 'GPL'], - ['MTI', 'MIT'], - ['Mozilla Public License', 'MPL'], - ['WTH', 'WTF'], - ['-License', ''] -]; - -var TRANSPOSED = 0; -var CORRECT = 1; - -// Simple corrections to nearly valid identifiers. -var transforms = [ - // e.g. 'mit' - function(argument) { - return argument.toUpperCase(); - }, - // e.g. 'MIT ' - function(argument) { - return argument.trim(); - }, - // e.g. 'M.I.T.' - function(argument) { - return argument.replace(/\./g, ''); - }, - // e.g. 'Apache- 2.0' - function(argument) { - return argument.replace(/\s+/g, ''); - }, - // e.g. 'CC BY 4.0'' - function(argument) { - return argument.replace(/\s+/g, '-'); - }, - // e.g. 'LGPLv2.1' - function(argument) { - return argument.replace('v', '-'); - }, - // e.g. 'Apache 2.0' - function(argument) { - return argument.replace(/,?\s*(\d)/, '-$1'); - }, - // e.g. 'GPL 2' - function(argument) { - return argument.replace(/,?\s*(\d)/, '-$1.0'); - }, - // e.g. 'Apache Version 2.0' - function(argument) { - return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2'); - }, - // e.g. 'Apache Version 2' - function(argument) { - return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0'); - }, - // e.g. 'ZLIB' - function(argument) { - return argument[0].toUpperCase() + argument.slice(1); - }, - // e.g. 'MPL/2.0' - function(argument) { - return argument.replace('/', '-'); - }, - // e.g. 'Apache 2' - function(argument) { - return argument - .replace(/\s*V\s*(\d)/, '-$1') - .replace(/(\d)$/, '$1.0'); - }, - // e.g. 'GPL-2.0-' - function(argument) { - return argument.slice(0, argument.length - 1); - }, - // e.g. 'GPL2' - function(argument) { - return argument.replace(/(\d)$/, '-$1.0'); - }, - // e.g. 'BSD 3' - function(argument) { - return argument.replace(/(-| )?(\d)$/, '-$2-Clause'); - }, - // e.g. 'BSD clause 3' - function(argument) { - return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause'); - }, - // e.g. 'BY-NC-4.0' - function(argument) { - return 'CC-' + argument; - }, - // e.g. 'BY-NC' - function(argument) { - return 'CC-' + argument + '-4.0'; - }, - // e.g. 'Attribution-NonCommercial' - function(argument) { - return argument - .replace('Attribution', 'BY') - .replace('NonCommercial', 'NC') - .replace('NoDerivatives', 'ND') - .replace(/ (\d)/, '-$1') - .replace(/ ?International/, ''); - }, - // e.g. 'Attribution-NonCommercial' - function(argument) { - return 'CC-' + - argument - .replace('Attribution', 'BY') - .replace('NonCommercial', 'NC') - .replace('NoDerivatives', 'ND') - .replace(/ (\d)/, '-$1') - .replace(/ ?International/, '') + - '-4.0'; - } -]; - -// If all else fails, guess that strings containing certain substrings -// meant to identify certain licenses. -var lastResorts = [ - ['UNLI', 'Unlicense'], - ['WTF', 'WTFPL'], - ['2 CLAUSE', 'BSD-2-Clause'], - ['2-CLAUSE', 'BSD-2-Clause'], - ['3 CLAUSE', 'BSD-3-Clause'], - ['3-CLAUSE', 'BSD-3-Clause'], - ['AFFERO', 'AGPL-3.0'], - ['AGPL', 'AGPL-3.0'], - ['APACHE', 'Apache-2.0'], - ['ARTISTIC', 'Artistic-2.0'], - ['Affero', 'AGPL-3.0'], - ['BEER', 'Beerware'], - ['BOOST', 'BSL-1.0'], - ['BSD', 'BSD-2-Clause'], - ['ECLIPSE', 'EPL-1.0'], - ['FUCK', 'WTFPL'], - ['GNU', 'GPL-3.0'], - ['LGPL', 'LGPL-3.0'], - ['GPL', 'GPL-3.0'], - ['MIT', 'MIT'], - ['MPL', 'MPL-2.0'], - ['X11', 'X11'], - ['ZLIB', 'Zlib'] -]; - -var SUBSTRING = 0; -var IDENTIFIER = 1; -var validTransformation = function(identifier) { - for (var i = 0; i < transforms.length; i++) { - var transformed = transforms[i](identifier); - if (transformed !== identifier && valid(transformed)) { - return transformed; +if (true) { +exports.parser = spdxparse; +exports.Parser = spdxparse.Parser; +exports.parse = function () { return spdxparse.parse.apply(spdxparse, arguments); }; +exports.main = function commonjsMain(args) { + if (!args[1]) { + console.log('Usage: '+args[0]+' FILE'); + process.exit(1); } - } - return null; + var source = __webpack_require__("fs").readFileSync(__webpack_require__("path").normalize(args[1]), "utf8"); + return exports.parser.parse(source); }; +if ( true && __webpack_require__.c[__webpack_require__.s] === module) { + exports.main(process.argv.slice(1)); +} +} -var validLastResort = function(identifier) { - var upperCased = identifier.toUpperCase(); - for (var i = 0; i < lastResorts.length; i++) { - var lastResort = lastResorts[i]; - if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) { - return lastResort[IDENTIFIER]; - } - } - return null; -}; +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__("../../node_modules/webpack/buildin/module.js")(module))) -var anyCorrection = function(identifier, check) { - for (var i = 0; i < transpositions.length; i++) { - var transposition = transpositions[i]; - var transposed = transposition[TRANSPOSED]; - if (identifier.indexOf(transposed) > -1) { - var corrected = identifier.replace( - transposed, - transposition[CORRECT] - ); - var checked = check(corrected); - if (checked !== null) { - return checked; - } - } - } - return null; -}; +/***/ }), -module.exports = function(identifier) { - identifier = identifier.replace(/\+$/, ''); - if (valid(identifier)) { - return identifier; - } - var transformed = validTransformation(identifier); - if (transformed !== null) { - return transformed; - } - transformed = anyCorrection(identifier, function(argument) { - if (valid(argument)) { - return argument; - } - return validTransformation(argument); - }); - if (transformed !== null) { - return transformed; - } - transformed = validLastResort(identifier); - if (transformed !== null) { - return transformed; - } - transformed = anyCorrection(identifier, validLastResort); - if (transformed !== null) { - return transformed; - } - return null; -}; +/***/ "../../node_modules/validate-npm-package-license/node_modules/spdx-license-ids/spdx-license-ids.json": +/***/ (function(module) { +module.exports = JSON.parse("[\"Glide\",\"Abstyles\",\"AFL-1.1\",\"AFL-1.2\",\"AFL-2.0\",\"AFL-2.1\",\"AFL-3.0\",\"AMPAS\",\"APL-1.0\",\"Adobe-Glyph\",\"APAFML\",\"Adobe-2006\",\"AGPL-1.0\",\"Afmparse\",\"Aladdin\",\"ADSL\",\"AMDPLPA\",\"ANTLR-PD\",\"Apache-1.0\",\"Apache-1.1\",\"Apache-2.0\",\"AML\",\"APSL-1.0\",\"APSL-1.1\",\"APSL-1.2\",\"APSL-2.0\",\"Artistic-1.0\",\"Artistic-1.0-Perl\",\"Artistic-1.0-cl8\",\"Artistic-2.0\",\"AAL\",\"Bahyph\",\"Barr\",\"Beerware\",\"BitTorrent-1.0\",\"BitTorrent-1.1\",\"BSL-1.0\",\"Borceux\",\"BSD-2-Clause\",\"BSD-2-Clause-FreeBSD\",\"BSD-2-Clause-NetBSD\",\"BSD-3-Clause\",\"BSD-3-Clause-Clear\",\"BSD-4-Clause\",\"BSD-Protection\",\"BSD-Source-Code\",\"BSD-3-Clause-Attribution\",\"0BSD\",\"BSD-4-Clause-UC\",\"bzip2-1.0.5\",\"bzip2-1.0.6\",\"Caldera\",\"CECILL-1.0\",\"CECILL-1.1\",\"CECILL-2.0\",\"CECILL-2.1\",\"CECILL-B\",\"CECILL-C\",\"ClArtistic\",\"MIT-CMU\",\"CNRI-Jython\",\"CNRI-Python\",\"CNRI-Python-GPL-Compatible\",\"CPOL-1.02\",\"CDDL-1.0\",\"CDDL-1.1\",\"CPAL-1.0\",\"CPL-1.0\",\"CATOSL-1.1\",\"Condor-1.1\",\"CC-BY-1.0\",\"CC-BY-2.0\",\"CC-BY-2.5\",\"CC-BY-3.0\",\"CC-BY-4.0\",\"CC-BY-ND-1.0\",\"CC-BY-ND-2.0\",\"CC-BY-ND-2.5\",\"CC-BY-ND-3.0\",\"CC-BY-ND-4.0\",\"CC-BY-NC-1.0\",\"CC-BY-NC-2.0\",\"CC-BY-NC-2.5\",\"CC-BY-NC-3.0\",\"CC-BY-NC-4.0\",\"CC-BY-NC-ND-1.0\",\"CC-BY-NC-ND-2.0\",\"CC-BY-NC-ND-2.5\",\"CC-BY-NC-ND-3.0\",\"CC-BY-NC-ND-4.0\",\"CC-BY-NC-SA-1.0\",\"CC-BY-NC-SA-2.0\",\"CC-BY-NC-SA-2.5\",\"CC-BY-NC-SA-3.0\",\"CC-BY-NC-SA-4.0\",\"CC-BY-SA-1.0\",\"CC-BY-SA-2.0\",\"CC-BY-SA-2.5\",\"CC-BY-SA-3.0\",\"CC-BY-SA-4.0\",\"CC0-1.0\",\"Crossword\",\"CrystalStacker\",\"CUA-OPL-1.0\",\"Cube\",\"curl\",\"D-FSL-1.0\",\"diffmark\",\"WTFPL\",\"DOC\",\"Dotseqn\",\"DSDP\",\"dvipdfm\",\"EPL-1.0\",\"ECL-1.0\",\"ECL-2.0\",\"eGenix\",\"EFL-1.0\",\"EFL-2.0\",\"MIT-advertising\",\"MIT-enna\",\"Entessa\",\"ErlPL-1.1\",\"EUDatagrid\",\"EUPL-1.0\",\"EUPL-1.1\",\"Eurosym\",\"Fair\",\"MIT-feh\",\"Frameworx-1.0\",\"FreeImage\",\"FTL\",\"FSFAP\",\"FSFUL\",\"FSFULLR\",\"Giftware\",\"GL2PS\",\"Glulxe\",\"AGPL-3.0\",\"GFDL-1.1\",\"GFDL-1.2\",\"GFDL-1.3\",\"GPL-1.0\",\"GPL-2.0\",\"GPL-3.0\",\"LGPL-2.1\",\"LGPL-3.0\",\"LGPL-2.0\",\"gnuplot\",\"gSOAP-1.3b\",\"HaskellReport\",\"HPND\",\"IBM-pibs\",\"IPL-1.0\",\"ICU\",\"ImageMagick\",\"iMatix\",\"Imlib2\",\"IJG\",\"Info-ZIP\",\"Intel-ACPI\",\"Intel\",\"Interbase-1.0\",\"IPA\",\"ISC\",\"JasPer-2.0\",\"JSON\",\"LPPL-1.0\",\"LPPL-1.1\",\"LPPL-1.2\",\"LPPL-1.3a\",\"LPPL-1.3c\",\"Latex2e\",\"BSD-3-Clause-LBNL\",\"Leptonica\",\"LGPLLR\",\"Libpng\",\"libtiff\",\"LAL-1.2\",\"LAL-1.3\",\"LiLiQ-P-1.1\",\"LiLiQ-Rplus-1.1\",\"LiLiQ-R-1.1\",\"LPL-1.02\",\"LPL-1.0\",\"MakeIndex\",\"MTLL\",\"MS-PL\",\"MS-RL\",\"MirOS\",\"MITNFA\",\"MIT\",\"Motosoto\",\"MPL-1.0\",\"MPL-1.1\",\"MPL-2.0\",\"MPL-2.0-no-copyleft-exception\",\"mpich2\",\"Multics\",\"Mup\",\"NASA-1.3\",\"Naumen\",\"NBPL-1.0\",\"NetCDF\",\"NGPL\",\"NOSL\",\"NPL-1.0\",\"NPL-1.1\",\"Newsletr\",\"NLPL\",\"Nokia\",\"NPOSL-3.0\",\"NLOD-1.0\",\"Noweb\",\"NRL\",\"NTP\",\"Nunit\",\"OCLC-2.0\",\"ODbL-1.0\",\"PDDL-1.0\",\"OCCT-PL\",\"OGTSL\",\"OLDAP-2.2.2\",\"OLDAP-1.1\",\"OLDAP-1.2\",\"OLDAP-1.3\",\"OLDAP-1.4\",\"OLDAP-2.0\",\"OLDAP-2.0.1\",\"OLDAP-2.1\",\"OLDAP-2.2\",\"OLDAP-2.2.1\",\"OLDAP-2.3\",\"OLDAP-2.4\",\"OLDAP-2.5\",\"OLDAP-2.6\",\"OLDAP-2.7\",\"OLDAP-2.8\",\"OML\",\"OPL-1.0\",\"OSL-1.0\",\"OSL-1.1\",\"OSL-2.0\",\"OSL-2.1\",\"OSL-3.0\",\"OpenSSL\",\"OSET-PL-2.1\",\"PHP-3.0\",\"PHP-3.01\",\"Plexus\",\"PostgreSQL\",\"psfrag\",\"psutils\",\"Python-2.0\",\"QPL-1.0\",\"Qhull\",\"Rdisc\",\"RPSL-1.0\",\"RPL-1.1\",\"RPL-1.5\",\"RHeCos-1.1\",\"RSCPL\",\"RSA-MD\",\"Ruby\",\"SAX-PD\",\"Saxpath\",\"SCEA\",\"SWL\",\"SMPPL\",\"Sendmail\",\"SGI-B-1.0\",\"SGI-B-1.1\",\"SGI-B-2.0\",\"OFL-1.0\",\"OFL-1.1\",\"SimPL-2.0\",\"Sleepycat\",\"SNIA\",\"Spencer-86\",\"Spencer-94\",\"Spencer-99\",\"SMLNJ\",\"SugarCRM-1.1.3\",\"SISSL\",\"SISSL-1.2\",\"SPL-1.0\",\"Watcom-1.0\",\"TCL\",\"Unlicense\",\"TMate\",\"TORQUE-1.1\",\"TOSL\",\"Unicode-TOU\",\"UPL-1.0\",\"NCSA\",\"Vim\",\"VOSTROM\",\"VSL-1.0\",\"W3C-19980720\",\"W3C\",\"Wsuipa\",\"Xnet\",\"X11\",\"Xerox\",\"XFree86-1.1\",\"xinetd\",\"xpp\",\"XSkat\",\"YPL-1.0\",\"YPL-1.1\",\"Zed\",\"Zend-2.0\",\"Zimbra-1.3\",\"Zimbra-1.4\",\"Zlib\",\"zlib-acknowledgement\",\"ZPL-1.1\",\"ZPL-2.0\",\"ZPL-2.1\",\"BSD-3-Clause-No-Nuclear-License\",\"BSD-3-Clause-No-Nuclear-Warranty\",\"BSD-3-Clause-No-Nuclear-License-2014\",\"eCos-2.0\",\"GPL-2.0-with-autoconf-exception\",\"GPL-2.0-with-bison-exception\",\"GPL-2.0-with-classpath-exception\",\"GPL-2.0-with-font-exception\",\"GPL-2.0-with-GCC-exception\",\"GPL-3.0-with-autoconf-exception\",\"GPL-3.0-with-GCC-exception\",\"StandardML-NJ\",\"WXwindows\"]"); /***/ }), diff --git a/packages/kbn-react-field/BUILD.bazel b/packages/kbn-react-field/BUILD.bazel index 07ecbcb61db26..1603fdc3158fc 100644 --- a/packages/kbn-react-field/BUILD.bazel +++ b/packages/kbn-react-field/BUILD.bazel @@ -44,6 +44,7 @@ TYPES_DEPS = [ "@npm//@types/classnames", "@npm//@types/jest", "@npm//@types/prop-types", + "@npm//@types/node", "@npm//@types/react", ] @@ -83,6 +84,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-react-field/tsconfig.json b/packages/kbn-react-field/tsconfig.json index f91c1f4c54d74..76ce92166b5d8 100644 --- a/packages/kbn-react-field/tsconfig.json +++ b/packages/kbn-react-field/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "types": [ diff --git a/packages/kbn-rule-data-utils/BUILD.bazel b/packages/kbn-rule-data-utils/BUILD.bazel index d4dc6577c02b4..b9f83be373674 100644 --- a/packages/kbn-rule-data-utils/BUILD.bazel +++ b/packages/kbn-rule-data-utils/BUILD.bazel @@ -66,8 +66,8 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, - incremental = False, out_dir = "target_types", root_dir = "src", tsconfig = ":tsconfig", diff --git a/packages/kbn-rule-data-utils/tsconfig.json b/packages/kbn-rule-data-utils/tsconfig.json index 05947e9286a74..8b8aef40e93f1 100644 --- a/packages/kbn-rule-data-utils/tsconfig.json +++ b/packages/kbn-rule-data-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "incremental": false, "outDir": "./target_types", diff --git a/packages/kbn-scalability-simulation-generator/BUILD.bazel b/packages/kbn-scalability-simulation-generator/BUILD.bazel index bd4b6a3217412..57464d684874c 100644 --- a/packages/kbn-scalability-simulation-generator/BUILD.bazel +++ b/packages/kbn-scalability-simulation-generator/BUILD.bazel @@ -79,6 +79,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-scalability-simulation-generator/tsconfig.json b/packages/kbn-scalability-simulation-generator/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-scalability-simulation-generator/tsconfig.json +++ b/packages/kbn-scalability-simulation-generator/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-autocomplete/BUILD.bazel b/packages/kbn-securitysolution-autocomplete/BUILD.bazel index d8de5ce23ea97..433590db8b27c 100644 --- a/packages/kbn-securitysolution-autocomplete/BUILD.bazel +++ b/packages/kbn-securitysolution-autocomplete/BUILD.bazel @@ -92,6 +92,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-autocomplete/tsconfig.json b/packages/kbn-securitysolution-autocomplete/tsconfig.json index dfe59c663d3e1..56db519b1a4c1 100644 --- a/packages/kbn-securitysolution-autocomplete/tsconfig.json +++ b/packages/kbn-securitysolution-autocomplete/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-es-utils/BUILD.bazel b/packages/kbn-securitysolution-es-utils/BUILD.bazel index e36409f464b3e..0b654730c5bd1 100644 --- a/packages/kbn-securitysolution-es-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-es-utils/BUILD.bazel @@ -64,6 +64,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-es-utils/tsconfig.json b/packages/kbn-securitysolution-es-utils/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-es-utils/tsconfig.json +++ b/packages/kbn-securitysolution-es-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-hook-utils/BUILD.bazel b/packages/kbn-securitysolution-hook-utils/BUILD.bazel index 363d4f688783d..591eb344c12ad 100644 --- a/packages/kbn-securitysolution-hook-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-hook-utils/BUILD.bazel @@ -71,6 +71,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-hook-utils/tsconfig.json b/packages/kbn-securitysolution-hook-utils/tsconfig.json index 6a1981335d68e..acd0e14795b57 100644 --- a/packages/kbn-securitysolution-hook-utils/tsconfig.json +++ b/packages/kbn-securitysolution-hook-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel b/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel index 0a06de87e8303..ae2d9820a0e08 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-alerting-types/BUILD.bazel @@ -74,6 +74,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-io-ts-alerting-types/tsconfig.json b/packages/kbn-securitysolution-io-ts-alerting-types/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-io-ts-alerting-types/tsconfig.json +++ b/packages/kbn-securitysolution-io-ts-alerting-types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel b/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel index d4ba51713d7e3..7216161a95a2a 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-list-types/BUILD.bazel @@ -74,6 +74,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-io-ts-list-types/tsconfig.json b/packages/kbn-securitysolution-io-ts-list-types/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/tsconfig.json +++ b/packages/kbn-securitysolution-io-ts-list-types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-io-ts-types/BUILD.bazel b/packages/kbn-securitysolution-io-ts-types/BUILD.bazel index 794eab1635b73..f1c285d215e03 100644 --- a/packages/kbn-securitysolution-io-ts-types/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-types/BUILD.bazel @@ -72,6 +72,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-io-ts-types/tsconfig.json b/packages/kbn-securitysolution-io-ts-types/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-io-ts-types/tsconfig.json +++ b/packages/kbn-securitysolution-io-ts-types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel b/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel index ce8c3efe8f782..126a192041fea 100644 --- a/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-io-ts-utils/BUILD.bazel @@ -75,6 +75,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-io-ts-utils/tsconfig.json b/packages/kbn-securitysolution-io-ts-utils/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-io-ts-utils/tsconfig.json +++ b/packages/kbn-securitysolution-io-ts-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-list-api/BUILD.bazel b/packages/kbn-securitysolution-list-api/BUILD.bazel index c73b9b4ea7503..172bd386f50e1 100644 --- a/packages/kbn-securitysolution-list-api/BUILD.bazel +++ b/packages/kbn-securitysolution-list-api/BUILD.bazel @@ -74,6 +74,7 @@ ts_project( deps = TYPES_DEPS, args = ["--pretty"], declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-list-api/tsconfig.json b/packages/kbn-securitysolution-list-api/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-list-api/tsconfig.json +++ b/packages/kbn-securitysolution-list-api/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-list-constants/BUILD.bazel b/packages/kbn-securitysolution-list-constants/BUILD.bazel index 339743721bc84..4853af65d6ec0 100644 --- a/packages/kbn-securitysolution-list-constants/BUILD.bazel +++ b/packages/kbn-securitysolution-list-constants/BUILD.bazel @@ -62,6 +62,7 @@ ts_project( deps = TYPES_DEPS, args = ["--pretty"], declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-list-constants/tsconfig.json b/packages/kbn-securitysolution-list-constants/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-list-constants/tsconfig.json +++ b/packages/kbn-securitysolution-list-constants/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-list-hooks/BUILD.bazel b/packages/kbn-securitysolution-list-hooks/BUILD.bazel index 1832340e607e7..d383805a3fe31 100644 --- a/packages/kbn-securitysolution-list-hooks/BUILD.bazel +++ b/packages/kbn-securitysolution-list-hooks/BUILD.bazel @@ -81,6 +81,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-list-hooks/tsconfig.json b/packages/kbn-securitysolution-list-hooks/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-list-hooks/tsconfig.json +++ b/packages/kbn-securitysolution-list-hooks/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-list-utils/BUILD.bazel b/packages/kbn-securitysolution-list-utils/BUILD.bazel index 642e4a970aca2..ed9a07b940243 100644 --- a/packages/kbn-securitysolution-list-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-list-utils/BUILD.bazel @@ -81,6 +81,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-list-utils/tsconfig.json b/packages/kbn-securitysolution-list-utils/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-list-utils/tsconfig.json +++ b/packages/kbn-securitysolution-list-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-rules/BUILD.bazel b/packages/kbn-securitysolution-rules/BUILD.bazel index 31b8fa8679312..81fb15aa02bad 100644 --- a/packages/kbn-securitysolution-rules/BUILD.bazel +++ b/packages/kbn-securitysolution-rules/BUILD.bazel @@ -69,6 +69,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-rules/tsconfig.json b/packages/kbn-securitysolution-rules/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-rules/tsconfig.json +++ b/packages/kbn-securitysolution-rules/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-t-grid/BUILD.bazel b/packages/kbn-securitysolution-t-grid/BUILD.bazel index 6ee620199a6e6..3be379f11e425 100644 --- a/packages/kbn-securitysolution-t-grid/BUILD.bazel +++ b/packages/kbn-securitysolution-t-grid/BUILD.bazel @@ -71,6 +71,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-t-grid/tsconfig.json b/packages/kbn-securitysolution-t-grid/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-t-grid/tsconfig.json +++ b/packages/kbn-securitysolution-t-grid/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-securitysolution-utils/BUILD.bazel b/packages/kbn-securitysolution-utils/BUILD.bazel index 1842e5d1a523f..825e595113979 100644 --- a/packages/kbn-securitysolution-utils/BUILD.bazel +++ b/packages/kbn-securitysolution-utils/BUILD.bazel @@ -69,6 +69,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-securitysolution-utils/tsconfig.json b/packages/kbn-securitysolution-utils/tsconfig.json index 305954d669b75..adfa06519038c 100644 --- a/packages/kbn-securitysolution-utils/tsconfig.json +++ b/packages/kbn-securitysolution-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-server-http-tools/BUILD.bazel b/packages/kbn-server-http-tools/BUILD.bazel index 29ca48adc566e..8fb30a5c78176 100644 --- a/packages/kbn-server-http-tools/BUILD.bazel +++ b/packages/kbn-server-http-tools/BUILD.bazel @@ -71,6 +71,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-server-http-tools/tsconfig.json b/packages/kbn-server-http-tools/tsconfig.json index c89835eada818..8c5a21016a00e 100644 --- a/packages/kbn-server-http-tools/tsconfig.json +++ b/packages/kbn-server-http-tools/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target/types", "rootDir": "src", diff --git a/packages/kbn-server-route-repository/BUILD.bazel b/packages/kbn-server-route-repository/BUILD.bazel index b635362ae1521..9c31a10edd355 100644 --- a/packages/kbn-server-route-repository/BUILD.bazel +++ b/packages/kbn-server-route-repository/BUILD.bazel @@ -40,6 +40,7 @@ TYPES_DEPS = [ "//packages/kbn-io-ts-utils:npm_module_types", "@npm//@hapi/boom", "@npm//fp-ts", + "@npm//io-ts", "@npm//utility-types", "@npm//@types/jest", "@npm//@types/lodash", @@ -78,6 +79,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-server-route-repository/tsconfig.json b/packages/kbn-server-route-repository/tsconfig.json index db908eacc6c35..c967742c70c3c 100644 --- a/packages/kbn-server-route-repository/tsconfig.json +++ b/packages/kbn-server-route-repository/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", diff --git a/packages/kbn-shared-ux-components/BUILD.bazel b/packages/kbn-shared-ux-components/BUILD.bazel index 20d71557fef9a..03468daf75fb8 100644 --- a/packages/kbn-shared-ux-components/BUILD.bazel +++ b/packages/kbn-shared-ux-components/BUILD.bazel @@ -122,6 +122,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-shared-ux-components/tsconfig.json b/packages/kbn-shared-ux-components/tsconfig.json index 4d3092e024c0b..dd55bdb28a632 100644 --- a/packages/kbn-shared-ux-components/tsconfig.json +++ b/packages/kbn-shared-ux-components/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-shared-ux-services/BUILD.bazel b/packages/kbn-shared-ux-services/BUILD.bazel index 0345e98cd7123..12aa766961c67 100755 --- a/packages/kbn-shared-ux-services/BUILD.bazel +++ b/packages/kbn-shared-ux-services/BUILD.bazel @@ -85,6 +85,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-shared-ux-services/tsconfig.json b/packages/kbn-shared-ux-services/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100755 --- a/packages/kbn-shared-ux-services/tsconfig.json +++ b/packages/kbn-shared-ux-services/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-shared-ux-storybook/BUILD.bazel b/packages/kbn-shared-ux-storybook/BUILD.bazel index d9d0848aea675..a8a3373d8ce3f 100644 --- a/packages/kbn-shared-ux-storybook/BUILD.bazel +++ b/packages/kbn-shared-ux-storybook/BUILD.bazel @@ -91,6 +91,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-shared-ux-storybook/tsconfig.json b/packages/kbn-shared-ux-storybook/tsconfig.json index 06df7f9138501..b26c95a01dfd8 100644 --- a/packages/kbn-shared-ux-storybook/tsconfig.json +++ b/packages/kbn-shared-ux-storybook/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-shared-ux-utility/BUILD.bazel b/packages/kbn-shared-ux-utility/BUILD.bazel index b4008aa43f1ac..e2a6046e00b2c 100644 --- a/packages/kbn-shared-ux-utility/BUILD.bazel +++ b/packages/kbn-shared-ux-utility/BUILD.bazel @@ -89,6 +89,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-shared-ux-utility/tsconfig.json b/packages/kbn-shared-ux-utility/tsconfig.json index cca91264519cc..98f0b6b6b5ae1 100644 --- a/packages/kbn-shared-ux-utility/tsconfig.json +++ b/packages/kbn-shared-ux-utility/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-sort-package-json/BUILD.bazel b/packages/kbn-sort-package-json/BUILD.bazel index 515e9009615c8..35aa5f0f48a6c 100644 --- a/packages/kbn-sort-package-json/BUILD.bazel +++ b/packages/kbn-sort-package-json/BUILD.bazel @@ -75,6 +75,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-sort-package-json/tsconfig.json b/packages/kbn-sort-package-json/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-sort-package-json/tsconfig.json +++ b/packages/kbn-sort-package-json/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-std/BUILD.bazel b/packages/kbn-std/BUILD.bazel index 08cfa2a2a1308..13bd3bef61cd1 100644 --- a/packages/kbn-std/BUILD.bazel +++ b/packages/kbn-std/BUILD.bazel @@ -66,6 +66,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-std/tsconfig.json b/packages/kbn-std/tsconfig.json index 04d1a54cc3951..a17f7ccb81cc8 100644 --- a/packages/kbn-std/tsconfig.json +++ b/packages/kbn-std/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", diff --git a/packages/kbn-stdio-dev-helpers/BUILD.bazel b/packages/kbn-stdio-dev-helpers/BUILD.bazel index 05b76bbc0508d..e125db84b843c 100644 --- a/packages/kbn-stdio-dev-helpers/BUILD.bazel +++ b/packages/kbn-stdio-dev-helpers/BUILD.bazel @@ -77,6 +77,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-stdio-dev-helpers/tsconfig.json b/packages/kbn-stdio-dev-helpers/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-stdio-dev-helpers/tsconfig.json +++ b/packages/kbn-stdio-dev-helpers/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-storybook/BUILD.bazel b/packages/kbn-storybook/BUILD.bazel index afbbc6df41e8d..efcab362c7491 100644 --- a/packages/kbn-storybook/BUILD.bazel +++ b/packages/kbn-storybook/BUILD.bazel @@ -93,6 +93,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-storybook/tsconfig.json b/packages/kbn-storybook/tsconfig.json index 1f7bc67539b44..b60613348ad7e 100644 --- a/packages/kbn-storybook/tsconfig.json +++ b/packages/kbn-storybook/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "incremental": false, "outDir": "target_types", diff --git a/packages/kbn-synthetic-package-map/tsconfig.json b/packages/kbn-synthetic-package-map/tsconfig.json index 7b74a1e555c86..7e53dd39bce02 100644 --- a/packages/kbn-synthetic-package-map/tsconfig.json +++ b/packages/kbn-synthetic-package-map/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "stripInternal": false, diff --git a/packages/kbn-telemetry-tools/BUILD.bazel b/packages/kbn-telemetry-tools/BUILD.bazel index 1c9f6113851ff..2b828fd1bd071 100644 --- a/packages/kbn-telemetry-tools/BUILD.bazel +++ b/packages/kbn-telemetry-tools/BUILD.bazel @@ -74,6 +74,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-telemetry-tools/tsconfig.json b/packages/kbn-telemetry-tools/tsconfig.json index 25e1d341ac07b..8189879390e78 100644 --- a/packages/kbn-telemetry-tools/tsconfig.json +++ b/packages/kbn-telemetry-tools/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "isolatedModules": true, "outDir": "./target_types", diff --git a/packages/kbn-test-jest-helpers/BUILD.bazel b/packages/kbn-test-jest-helpers/BUILD.bazel index 85192829003e4..46a3df5ffd38a 100644 --- a/packages/kbn-test-jest-helpers/BUILD.bazel +++ b/packages/kbn-test-jest-helpers/BUILD.bazel @@ -135,6 +135,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-test-jest-helpers/tsconfig.json b/packages/kbn-test-jest-helpers/tsconfig.json index 72d996c69e2da..5abd6017d2098 100644 --- a/packages/kbn-test-jest-helpers/tsconfig.json +++ b/packages/kbn-test-jest-helpers/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "stripInternal": true, diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index 42ccc6cdd4641..e2824076e80b9 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -82,9 +82,9 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-dev-utils:npm_module_types", - "//packages/kbn-i18n-react:npm_module_types", "//packages/kbn-std:npm_module_types", "//packages/kbn-utils:npm_module_types", + "//packages/kbn-tooling-log:npm_module_types", "//packages/kbn-bazel-packages:npm_module_types", "@npm//@elastic/elasticsearch", "@npm//@jest/console", @@ -148,6 +148,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index aa9fb4f04135d..13628db6b95d7 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, + "declarationMap": true, "outDir": "./target_types", "stripInternal": true, "rootDir": "src", diff --git a/packages/kbn-tooling-log/BUILD.bazel b/packages/kbn-tooling-log/BUILD.bazel index e5d196bd4649c..3acf9108010f9 100644 --- a/packages/kbn-tooling-log/BUILD.bazel +++ b/packages/kbn-tooling-log/BUILD.bazel @@ -78,6 +78,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-tooling-log/tsconfig.json b/packages/kbn-tooling-log/tsconfig.json index a8cfc2cceb08b..789c6b3111115 100644 --- a/packages/kbn-tooling-log/tsconfig.json +++ b/packages/kbn-tooling-log/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/kbn-type-summarizer-cli/BUILD.bazel b/packages/kbn-type-summarizer-cli/BUILD.bazel new file mode 100644 index 0000000000000..310ecc59c8e45 --- /dev/null +++ b/packages/kbn-type-summarizer-cli/BUILD.bazel @@ -0,0 +1,133 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "directory_file_path", "js_library", "nodejs_binary") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-type-summarizer-cli" +PKG_REQUIRE_NAME = "@kbn/type-summarizer-cli" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ + "@npm//@babel/runtime", + "//packages/kbn-type-summarizer", + "//packages/kbn-type-summarizer-core", +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-type-summarizer:npm_module_types", + "//packages/kbn-type-summarizer-core:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +directory_file_path( + name = "bazel-cli-path", + directory = ":target_node", + path = "index.js", +) + +nodejs_binary( + name = "bazel-cli", + data = [":" + PKG_DIRNAME], + entry_point = ":bazel-cli-path", + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-type-summarizer-cli/README.md b/packages/kbn-type-summarizer-cli/README.md new file mode 100644 index 0000000000000..b35cfb615aaeb --- /dev/null +++ b/packages/kbn-type-summarizer-cli/README.md @@ -0,0 +1,9 @@ +# @kbn/type-summarizer-cli + +Exposes the` @kbn/type-summarizer` via a command line interface. Run `node scripts/type_summarizer` for information about running this command. + +## Example: + +```sh +node scripts/type_summarizer packages/kbn-type-summarizer-cli +``` diff --git a/packages/kbn-type-summarizer/src/lib/export_collector/index.ts b/packages/kbn-type-summarizer-cli/jest.config.js similarity index 72% rename from packages/kbn-type-summarizer/src/lib/export_collector/index.ts rename to packages/kbn-type-summarizer-cli/jest.config.js index a0447445ac20f..bbf8a9ab4449c 100644 --- a/packages/kbn-type-summarizer/src/lib/export_collector/index.ts +++ b/packages/kbn-type-summarizer-cli/jest.config.js @@ -6,6 +6,8 @@ * Side Public License, v 1. */ -export * from './exports_collector'; -export * from './collector_results'; -export * from './imported_symbol'; +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-type-summarizer-cli'], +}; diff --git a/packages/kbn-type-summarizer-cli/package.json b/packages/kbn-type-summarizer-cli/package.json new file mode 100644 index 0000000000000..fafc57c3c7d08 --- /dev/null +++ b/packages/kbn-type-summarizer-cli/package.json @@ -0,0 +1,10 @@ +{ + "name": "@kbn/type-summarizer-cli", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0", + "kibana": { + "devOnly": true + } +} diff --git a/packages/kbn-type-summarizer/src/lib/bazel_cli_config.ts b/packages/kbn-type-summarizer-cli/src/cli_config.ts similarity index 74% rename from packages/kbn-type-summarizer/src/lib/bazel_cli_config.ts rename to packages/kbn-type-summarizer-cli/src/cli_config.ts index 7527ae35d4357..50ca0e161536b 100644 --- a/packages/kbn-type-summarizer/src/lib/bazel_cli_config.ts +++ b/packages/kbn-type-summarizer-cli/src/cli_config.ts @@ -8,37 +8,18 @@ import Fs from 'fs'; -import { CliError } from './cli_error'; +import { CliError, Path } from '@kbn/type-summarizer-core'; import { parseCliFlags } from './cli_flags'; -import * as Path from './path'; - -const TYPE_SUMMARIZER_PACKAGES = [ - '@kbn/type-summarizer', - '@kbn/crypto', - '@kbn/generate', - '@kbn/mapbox-gl', - '@kbn/ace', - '@kbn/alerts', - '@kbn/analytics', - '@kbn/apm-config-loader', - '@kbn/apm-utils', - '@kbn/plugin-discovery', -]; - -type TypeSummarizerType = 'api-extractor' | 'type-summarizer'; -function isTypeSummarizerType(v: string): v is TypeSummarizerType { - return v === 'api-extractor' || v === 'type-summarizer'; -} const isString = (i: any): i is string => typeof i === 'string' && i.length > 0; -interface BazelCliConfig { +interface CliConfig { packageName: string; outputDir: string; tsconfigPath: string; inputPath: string; repoRelativePackageDir: string; - type: TypeSummarizerType; + dump: boolean; } function isKibanaRepo(dir: string) { @@ -51,7 +32,7 @@ function isKibanaRepo(dir: string) { } } -function findRepoRoot() { +export function findRepoRoot() { const start = Path.resolve(__dirname); let dir = start; while (true) { @@ -71,12 +52,9 @@ function findRepoRoot() { } } -export function parseBazelCliFlags(argv: string[]): BazelCliConfig[] { +function parseConfigFromFlags(argv: string[]): CliConfig { const { rawFlags, unknownFlags } = parseCliFlags(argv, { - string: ['use'], - default: { - use: 'api-extractor,type-summarizer', - }, + boolean: ['dump'], }); if (unknownFlags.length) { @@ -95,28 +73,22 @@ export function parseBazelCliFlags(argv: string[]): BazelCliConfig[] { throw new CliError(`extra positional arguments`, { showHelp: true }); } - const use = String(rawFlags.use || '') - .split(',') - .map((t) => t.trim()) - .filter(Boolean); - if (!use.every(isTypeSummarizerType)) { - throw new CliError(`invalid --use flag, expected "api-extractor" or "type-summarizer"`); - } - const packageDir = Path.resolve(relativePackagePath); const packageName: string = JSON.parse( Fs.readFileSync(Path.join(packageDir, 'package.json'), 'utf8') ).name; const repoRelativePackageDir = Path.relative(repoRoot, packageDir); - return use.map((type) => ({ - type, + const dump = !!rawFlags.dump; + + return { packageName, tsconfigPath: Path.join(repoRoot, repoRelativePackageDir, 'tsconfig.json'), inputPath: Path.join(repoRoot, 'node_modules', packageName, 'target_types/index.d.ts'), repoRelativePackageDir, - outputDir: Path.join(repoRoot, 'data/type-summarizer-output', type), - })); + outputDir: Path.join(repoRoot, 'data/type-summarizer-output'), + dump, + }; } function parseJsonFromCli(json: string) { @@ -144,7 +116,7 @@ function parseJsonFromCli(json: string) { } } -export function parseBazelCliJson(json: string): BazelCliConfig[] { +function parseConfigFromJson(json: string): CliConfig { const config = parseJsonFromCli(json); if (typeof config !== 'object' || config === null) { throw new CliError('config JSON must be an object'); @@ -187,21 +159,19 @@ export function parseBazelCliJson(json: string): BazelCliConfig[] { throw new CliError(`buildFilePath [${buildFilePath}] must be a relative path`); } - return [ - { - packageName, - outputDir: Path.resolve(outputDir), - tsconfigPath: Path.resolve(tsconfigPath), - inputPath: Path.resolve(inputPath), - repoRelativePackageDir: Path.dirname(buildFilePath), - type: TYPE_SUMMARIZER_PACKAGES.includes(packageName) ? 'type-summarizer' : 'api-extractor', - }, - ]; + return { + packageName, + outputDir: Path.resolve(outputDir), + tsconfigPath: Path.resolve(tsconfigPath), + inputPath: Path.resolve(inputPath), + repoRelativePackageDir: Path.dirname(buildFilePath), + dump: false, + }; } -export function parseBazelCliConfigs(argv: string[]): BazelCliConfig[] { +export function parseCliConfig(argv: string[]): CliConfig { if (typeof argv[0] === 'string' && argv[0].startsWith('{')) { - return parseBazelCliJson(argv[0]); + return parseConfigFromJson(argv[0]); } - return parseBazelCliFlags(argv); + return parseConfigFromFlags(argv); } diff --git a/packages/kbn-type-summarizer/src/lib/cli_flags.ts b/packages/kbn-type-summarizer-cli/src/cli_flags.ts similarity index 100% rename from packages/kbn-type-summarizer/src/lib/cli_flags.ts rename to packages/kbn-type-summarizer-cli/src/cli_flags.ts diff --git a/packages/kbn-type-summarizer-cli/src/index.ts b/packages/kbn-type-summarizer-cli/src/index.ts new file mode 100644 index 0000000000000..a0d2707fb2fc4 --- /dev/null +++ b/packages/kbn-type-summarizer-cli/src/index.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 Fsp from 'fs/promises'; + +import { Path } from '@kbn/type-summarizer-core'; +import { summarizePackage } from '@kbn/type-summarizer'; + +import { parseCliConfig } from './cli_config'; + +import { run } from './run'; + +const HELP = ` +Script called from bazel to create the summarized version of a package. When called by Bazel +config is passed as a JSON encoded object. To build config locally you can just pass a path to +a package directory. + +Usage: + + node scripts/type_summarizer +`; + +run( + async ({ argv, log }) => { + const { packageName, outputDir, inputPath, repoRelativePackageDir, tsconfigPath } = + parseCliConfig(argv); + + try { + await Fsp.rm(outputDir, { recursive: true }); + } catch (error) { + if (error && error.code !== 'ENOENT') { + throw error; + } + } + + await Fsp.mkdir(outputDir, { recursive: true }); + + // generate pkg json output + await Fsp.writeFile( + Path.join(outputDir, 'package.json'), + JSON.stringify( + { + name: `@types/${packageName.replaceAll('@', '').replaceAll('/', '__')}`, + description: 'Generated by @kbn/type-summarizer', + types: './index.d.ts', + private: true, + license: 'MIT', + version: '1.1.0', + }, + null, + 2 + ) + ); + + const sourceNode = await summarizePackage(log, { + dtsDir: Path.dirname(inputPath), + inputPath, + tsconfigPath, + repoRelativePackageDir, + }); + + const source = sourceNode.toStringWithSourceMap({ + file: 'index.d.ts', + sourceRoot: `../../../${Path.toNormal(repoRelativePackageDir)}`, + }); + + const code = `${source.code}${ + source.code.endsWith('\n') ? '' : '\n' + }//# sourceMappingURL=index.d.ts.map`; + + await Fsp.writeFile(Path.join(outputDir, 'index.d.ts'), code); + await Fsp.writeFile(Path.join(outputDir, 'index.d.ts.map'), JSON.stringify(source.map)); + + log.success('type summary created for', packageName); + }, + { + helpText: HELP, + defaultLogLevel: 'quiet', + } +); diff --git a/packages/kbn-type-summarizer/src/lib/run.ts b/packages/kbn-type-summarizer-cli/src/run.ts similarity index 87% rename from packages/kbn-type-summarizer/src/lib/run.ts rename to packages/kbn-type-summarizer-cli/src/run.ts index 4834c4d8aae9b..dfba870b359b9 100644 --- a/packages/kbn-type-summarizer/src/lib/run.ts +++ b/packages/kbn-type-summarizer-cli/src/run.ts @@ -8,9 +8,7 @@ import getopts from 'getopts'; -import { CliLog, LogLevel } from './log'; -import { toError } from './helpers/error'; -import { CliError } from './cli_error'; +import { CliLog, CliError, LogLevel, toError } from '@kbn/type-summarizer-core'; export interface RunContext { argv: string[]; @@ -26,6 +24,11 @@ export async function run(main: (ctx: RunContext) => Promise, options: Run const argv = process.argv.slice(2); const rawFlags = getopts(argv); + if (rawFlags.help) { + process.stdout.write(options.helpText); + process.exit(0); + } + const log = new CliLog( CliLog.pickLogLevelFromFlags(rawFlags, options.defaultLogLevel), process.stdout diff --git a/packages/kbn-type-summarizer-cli/tsconfig.json b/packages/kbn-type-summarizer-cli/tsconfig.json new file mode 100644 index 0000000000000..789c6b3111115 --- /dev/null +++ b/packages/kbn-type-summarizer-cli/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kbn-type-summarizer-core/BUILD.bazel b/packages/kbn-type-summarizer-core/BUILD.bazel new file mode 100644 index 0000000000000..c8caa7685823f --- /dev/null +++ b/packages/kbn-type-summarizer-core/BUILD.bazel @@ -0,0 +1,126 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "kbn-type-summarizer-core" +PKG_REQUIRE_NAME = "@kbn/type-summarizer-core" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ + "@npm//getopts", + "@npm//chalk", + "@npm//normalize-path", + "@npm//strip-ansi", + "@npm//tslib", + "@npm//typescript", +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/normalize-path", + "@npm//chalk", + "@npm//getopts", + "@npm//tslib", + "@npm//strip-ansi", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-type-summarizer-core/README.md b/packages/kbn-type-summarizer-core/README.md new file mode 100644 index 0000000000000..b9e10dadd8d6b --- /dev/null +++ b/packages/kbn-type-summarizer-core/README.md @@ -0,0 +1,3 @@ +# @kbn/type-summarizer-core + +Contains the common code needed for both `@kbn/type-summarizer` and `@kbn/type-summarizer-cli` \ No newline at end of file diff --git a/packages/kbn-type-summarizer/src/lib/export_collector/reference.ts b/packages/kbn-type-summarizer-core/jest.config.js similarity index 65% rename from packages/kbn-type-summarizer/src/lib/export_collector/reference.ts rename to packages/kbn-type-summarizer-core/jest.config.js index b664a457a24ad..1b0c701319036 100644 --- a/packages/kbn-type-summarizer/src/lib/export_collector/reference.ts +++ b/packages/kbn-type-summarizer-core/jest.config.js @@ -6,9 +6,8 @@ * Side Public License, v 1. */ -export type ReferenceKey = 'types' | 'lib'; - -export class Reference { - type = 'reference' as const; - constructor(public readonly key: ReferenceKey, public readonly name: string) {} -} +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-type-summarizer-core'], +}; diff --git a/packages/kbn-type-summarizer-core/package.json b/packages/kbn-type-summarizer-core/package.json new file mode 100644 index 0000000000000..a1dfb4fea8751 --- /dev/null +++ b/packages/kbn-type-summarizer-core/package.json @@ -0,0 +1,10 @@ +{ + "name": "@kbn/type-summarizer-core", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0", + "kibana": { + "devOnly": true + } +} diff --git a/packages/kbn-type-summarizer/src/lib/cli_error.ts b/packages/kbn-type-summarizer-core/src/cli_error.ts similarity index 82% rename from packages/kbn-type-summarizer/src/lib/cli_error.ts rename to packages/kbn-type-summarizer-core/src/cli_error.ts index 143d790612f61..c15e5478ea34f 100644 --- a/packages/kbn-type-summarizer/src/lib/cli_error.ts +++ b/packages/kbn-type-summarizer-core/src/cli_error.ts @@ -6,11 +6,17 @@ * Side Public License, v 1. */ +/** + * Options for customizing CliError instances + */ export interface CliErrorOptions { exitCode?: number; showHelp?: boolean; } +/** + * An error type with specicial behavior when it bubbles up all the way to the root of the CLI + */ export class CliError extends Error { public readonly exitCode: number; public readonly showHelp: boolean; diff --git a/packages/kbn-type-summarizer/src/lib/helpers/error.ts b/packages/kbn-type-summarizer-core/src/error.ts similarity index 78% rename from packages/kbn-type-summarizer/src/lib/helpers/error.ts rename to packages/kbn-type-summarizer-core/src/error.ts index f78eb29083b04..080fa8990ec46 100644 --- a/packages/kbn-type-summarizer/src/lib/helpers/error.ts +++ b/packages/kbn-type-summarizer-core/src/error.ts @@ -6,6 +6,9 @@ * Side Public License, v 1. */ +/** + * Convert an unknown thrown value to an Error instance if it isn't alread + */ export function toError(thrown: unknown) { if (thrown instanceof Error) { return thrown; @@ -14,6 +17,9 @@ export function toError(thrown: unknown) { return new Error(`${thrown} thrown`); } +/** + * Is this error instance a Node.js system error which has an error code attached? + */ export function isSystemError(error: Error): error is NodeJS.ErrnoException { return typeof (error as any).code === 'string'; } diff --git a/packages/kbn-type-summarizer/src/lib/helpers/fs.ts b/packages/kbn-type-summarizer-core/src/fs.ts similarity index 78% rename from packages/kbn-type-summarizer/src/lib/helpers/fs.ts rename to packages/kbn-type-summarizer-core/src/fs.ts index 092310c1e5db0..eafe789c9d05a 100644 --- a/packages/kbn-type-summarizer/src/lib/helpers/fs.ts +++ b/packages/kbn-type-summarizer-core/src/fs.ts @@ -9,10 +9,18 @@ import Fsp from 'fs/promises'; import { toError, isSystemError } from './error'; +/** + * Read a file, if the file doesn't exist return undefined. If any other + * error occurs they will be thrown. + */ export async function tryReadFile( path: string, encoding: 'utf-8' | 'utf8' ): Promise; +/** + * Read a file, if the file doesn't exist return undefined. If any other + * error occurs they will be thrown. + */ export async function tryReadFile(path: string, encoding?: BufferEncoding) { try { return await Fsp.readFile(path, encoding); diff --git a/packages/kbn-type-summarizer-core/src/index.ts b/packages/kbn-type-summarizer-core/src/index.ts new file mode 100644 index 0000000000000..5dfb6ab462dee --- /dev/null +++ b/packages/kbn-type-summarizer-core/src/index.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 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 type { LogLevel } from './log'; +export type { LogWriter, Logger } from './log'; +export { CliLog, TestLog } from './log'; + +export { isSystemError, toError } from './error'; +export { tryReadFile } from './fs'; +export { parseJson } from './json'; +export type { CliErrorOptions } from './cli_error'; +export { CliError } from './cli_error'; +export { + describeNode, + describeSymbol, + getKindName, + hasIdentifierName, + isAliasSymbol, +} from './ts_helpers'; +import * as Path from './path'; +export { Path }; +export { SetMap } from './set_map'; diff --git a/packages/kbn-type-summarizer/src/lib/helpers/json.test.ts b/packages/kbn-type-summarizer-core/src/json.test.ts similarity index 100% rename from packages/kbn-type-summarizer/src/lib/helpers/json.test.ts rename to packages/kbn-type-summarizer-core/src/json.test.ts diff --git a/packages/kbn-type-summarizer/src/lib/helpers/json.ts b/packages/kbn-type-summarizer-core/src/json.ts similarity index 88% rename from packages/kbn-type-summarizer/src/lib/helpers/json.ts rename to packages/kbn-type-summarizer-core/src/json.ts index ee2403bd9422c..dbabff5cbadd8 100644 --- a/packages/kbn-type-summarizer/src/lib/helpers/json.ts +++ b/packages/kbn-type-summarizer-core/src/json.ts @@ -8,6 +8,9 @@ import { toError } from './error'; +/** + * Parse JSON, but thrown a more meaningful error message when parsing fails + */ export function parseJson(json: string, from?: string) { try { return JSON.parse(json); diff --git a/packages/kbn-type-summarizer-core/src/log/cli_log.ts b/packages/kbn-type-summarizer-core/src/log/cli_log.ts new file mode 100644 index 0000000000000..35195dc1d2133 --- /dev/null +++ b/packages/kbn-type-summarizer-core/src/log/cli_log.ts @@ -0,0 +1,249 @@ +/* + * 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 Path from 'path'; +import Util from 'util'; + +import { bold, dim, blueBright, yellowBright, gray, bgRed } from 'chalk'; +import getopts from 'getopts'; +import ts from 'typescript'; +import stripAnsi from 'strip-ansi'; + +import { Logger } from './logger'; +import { describeNode, describeSymbol } from '../ts_helpers'; + +const LOG_LEVEL_RANKS = { + silent: 0, + quiet: 1, + info: 2, + debug: 3, + verbose: 4, +}; + +/** + * Level that the logger is running at, any message logged "above" this level will be dropped + */ +export type LogLevel = keyof typeof LOG_LEVEL_RANKS; + +const LOG_LEVELS = (Object.keys(LOG_LEVEL_RANKS) as LogLevel[]).sort( + (a, b) => LOG_LEVEL_RANKS[a] - LOG_LEVEL_RANKS[b] +); +const LOG_LEVELS_DESC = LOG_LEVELS.slice().reverse(); + +type LogLevelMap = { [k in LogLevel]: boolean }; + +const now = + typeof performance !== 'undefined' ? performance.now.bind(performance) : Date.now.bind(Date); + +const fmt = (prefix: string, msg: string, ...args: string[]) => { + const lines = Util.format(msg, ...args).split('\n'); + + let formatted = lines[0]; + if (lines.length > 1) { + const padding = ' '.repeat(stripAnsi(prefix).length + 1); + for (const line of lines.slice(1)) { + formatted += `\n${padding}${line}`; + } + } + + return `${prefix} ${formatted}\n`; +}; + +const fmtMs = (ms: number) => { + if (ms < 1) { + return dim(`${Math.floor(ms * 100)}µs`); + } + + if (ms <= 5) { + return dim(`${Math.round(ms)}ms`); + } + + if (ms <= 500) { + return `${Math.round(ms)}ms`; + } + + return bold.yellow(`${(ms / 1000).toFixed(2)}s`); +}; + +const fmtDesc = (desc: string | ts.Symbol | ts.Node) => { + if (typeof desc === 'string') { + return Path.isAbsolute(desc) ? Path.relative(process.cwd(), desc) : desc; + } + + return 'kind' in desc ? describeNode(desc) : describeSymbol(desc); +}; + +/** + * Interface of objects which receive log messages, often times points to stdout, but + * replaced with a log message collector in tests + */ +export interface LogWriter { + write(chunk: string): void; +} + +interface Step { + verboseSteps: Map; +} + +/** + * Logger which writes messages in a text format designed for CLIs + */ +export class CliLog implements Logger { + private indent = ''; + private readonly stepStack: Step[] = []; + + static parseLogLevel(level: LogLevel) { + if (!LOG_LEVELS.includes(level)) { + throw new Error('invalid log level'); + } + + const rank = LOG_LEVEL_RANKS[level]; + return Object.fromEntries( + LOG_LEVELS.map((l) => [l, LOG_LEVEL_RANKS[l] <= rank]) + ) as LogLevelMap; + } + + static pickLogLevelFromFlags( + flags: getopts.ParsedOptions, + defaultLogLevl: LogLevel = 'info' + ): LogLevel { + for (const level of LOG_LEVELS_DESC) { + if (Object.prototype.hasOwnProperty.call(flags, level) && flags[level] === true) { + return level; + } + } + + return defaultLogLevl; + } + + private readonly map: LogLevelMap; + constructor( + public readonly level: LogLevel, + private readonly writeTo: LogWriter, + private readonly writeTimes = true + ) { + this.map = CliLog.parseLogLevel(level); + } + + info(msg: string, ...args: any[]) { + if (this.map.info) { + this.writeTo.write(fmt(`${this.indent}${blueBright('info')}`, msg, ...args)); + } + } + + warn(msg: string, ...args: any[]) { + if (this.map.quiet) { + this.writeTo.write(fmt(`${this.indent}${yellowBright('warn')}`, msg, ...args)); + } + } + + error(msg: string, ...args: any[]) { + if (this.map.quiet) { + this.writeTo.write(fmt(`${this.indent}${bgRed.whiteBright('ERROR')}`, msg, ...args)); + } + } + + debug(msg: string, ...args: any[]) { + if (this.map.debug) { + this.writeTo.write(fmt(`${this.indent}${gray('debg')}`, msg, ...args)); + } + } + + verbose(msg: string, ...args: any[]) { + if (this.map.verbose) { + this.writeTo.write(fmt(`${this.indent}${dim('verb')}`, msg, ...args)); + } + } + + success(msg: string, ...args: any[]): void { + if (this.map.quiet) { + this.writeTo.write(fmt(`${this.indent}✅`, msg, ...args)); + } + } + + step(name: string, desc: ts.Symbol | ts.Node | string | null, block: () => T): T { + return this.stepImpl('debug', name, desc, block); + } + + verboseStep(name: string, desc: string | ts.Symbol | ts.Node | null, block: () => T): T { + if (!this.map.debug) { + return block(); + } + + if (!this.stepStack.length || this.map.verbose) { + return this.stepImpl('verbose', name, desc, block); + } + + const step = this.stepStack[0]; + const start = now(); + try { + return block(); + } finally { + const ms = now() - start; + const group = step.verboseSteps.get(name); + if (group) { + group.count += 1; + group.ms += ms; + } else { + step.verboseSteps.set(name, { + count: 1, + ms, + }); + } + } + } + + private stepImpl( + level: 'debug' | 'verbose', + name: string, + desc: string | ts.Symbol | ts.Node | null, + block: () => T + ): T { + if (!this.map[level]) { + return block(); + } + + if (desc !== null) { + this[level]('>', bold(name), dim(`-- ${fmtDesc(desc)}`)); + } else { + this[level]('>', bold(name)); + } + + const start = now(); + let success = true; + const prevIndent = this.indent; + this.indent = ' '.repeat(prevIndent.length + 4); + + const verboseSteps = new Map(); + this.stepStack.unshift({ verboseSteps }); + + try { + return block(); + } catch (error) { + success = false; + throw error; + } finally { + const ms = now() - start; + + this.stepStack.shift(); + if (verboseSteps.size) { + const summary = []; + for (const [step, { count, ms: m }] of verboseSteps) { + summary.push(`${step}x${count}${this.writeTimes ? `: ${fmtMs(m)}` : ''}`); + } + this[level](dim(`verbose steps:\n${summary.join('\n')}`)); + } + + if (this.writeTimes) { + this[level](success ? fmtMs(ms) : `‼️ ${fmtMs(ms)}`); + } + + this.indent = prevIndent; + } + } +} diff --git a/packages/kbn-type-summarizer/src/lib/export_collector/result_value.ts b/packages/kbn-type-summarizer-core/src/log/index.ts similarity index 59% rename from packages/kbn-type-summarizer/src/lib/export_collector/result_value.ts rename to packages/kbn-type-summarizer-core/src/log/index.ts index 839f5a6f99f82..fac2d627cc590 100644 --- a/packages/kbn-type-summarizer/src/lib/export_collector/result_value.ts +++ b/packages/kbn-type-summarizer-core/src/log/index.ts @@ -6,11 +6,7 @@ * Side Public License, v 1. */ -import { ValueNode } from '../ts_nodes'; -import { ExportInfo } from '../export_info'; - -export class ResultValue { - type = 'value' as const; - - constructor(public exportInfo: ExportInfo | undefined, public readonly node: ValueNode) {} -} +export type { Logger } from './logger'; +export type { LogWriter, LogLevel } from './cli_log'; +export { CliLog } from './cli_log'; +export { TestLog } from './test_log'; diff --git a/packages/kbn-type-summarizer/src/lib/log/logger.ts b/packages/kbn-type-summarizer-core/src/log/logger.ts similarity index 58% rename from packages/kbn-type-summarizer/src/lib/log/logger.ts rename to packages/kbn-type-summarizer-core/src/log/logger.ts index 76cb7fe525f6d..300b51c173c78 100644 --- a/packages/kbn-type-summarizer/src/lib/log/logger.ts +++ b/packages/kbn-type-summarizer-core/src/log/logger.ts @@ -6,8 +6,10 @@ * Side Public License, v 1. */ +import ts from 'typescript'; + /** - * Logger interface used by @kbn/type-summarizer + * Logger interface used by @kbn/type-summarizer-* packages */ export interface Logger { /** @@ -46,4 +48,24 @@ export interface Logger { * @param args any serializeable values you would like to be appended to the log message */ success(msg: string, ...args: any[]): void; + /** + * Write a message to the log indicating the beginning of a step, then run the passed + * block, any log output produced inside that step will be indented and at the end the + * duration of the step will be written. If the log level is below verbose then any + * "verbose steps" executed inside this step will be summaried by this step at the end + * as well. + * @param name a common name for steps of a specific type + * @param desc a specific name to describe the unique information about this step + * @param block the function body which defines this step + */ + step(name: string, desc: ts.Symbol | ts.Node | string | null, block: () => T): T; + /** + * Just like step(), except that unless the logging level is set to verbose the steps with + * the same name will be summaried at the end of the containing step, rather than logged + * directly. + * @param name a common name for steps of a specific type + * @param desc a specific name to describe the unique information about this step + * @param block the function body which defines this step + */ + verboseStep(name: string, desc: ts.Symbol | ts.Node | string | null, block: () => T): T; } diff --git a/packages/kbn-type-summarizer/src/lib/log/test_log.ts b/packages/kbn-type-summarizer-core/src/log/test_log.ts similarity index 63% rename from packages/kbn-type-summarizer/src/lib/log/test_log.ts rename to packages/kbn-type-summarizer-core/src/log/test_log.ts index 5062a8cbae841..8f05c8b609899 100644 --- a/packages/kbn-type-summarizer/src/lib/log/test_log.ts +++ b/packages/kbn-type-summarizer-core/src/log/test_log.ts @@ -6,15 +6,22 @@ * Side Public License, v 1. */ -import { CliLog, LogLevel } from './cli_log'; +import { CliLog } from './cli_log'; +/** + * Logger which collects messages in memory for testing + */ export class TestLog extends CliLog { messages: string[] = []; - constructor(level: LogLevel = 'verbose') { - super(level, { - write: (chunk) => { - this.messages.push(chunk); + constructor() { + super( + 'debug', + { + write: (chunk) => { + this.messages.push(chunk); + }, }, - }); + false + ); } } diff --git a/packages/kbn-type-summarizer-core/src/path.ts b/packages/kbn-type-summarizer-core/src/path.ts new file mode 100644 index 0000000000000..117a6f62e78cc --- /dev/null +++ b/packages/kbn-type-summarizer-core/src/path.ts @@ -0,0 +1,62 @@ +/* + * 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 Path from 'path'; + +import normalizePath from 'normalize-path'; +const cwd = normalizePath(process.cwd()); + +/** + * Convert a path to a relative path based on the current working directory. + * All paths returned are normalized + */ +export function cwdRelative(path: string) { + return relative(cwd, path); +} + +/** + * Convert a path to a relative path. All paths returned are normalized + */ +export function relative(from: string, to: string) { + return toNormal(Path.relative(from, to)); +} + +/** + * Join segments into a single path. All paths returned are normalized + */ +export function join(...segments: string[]) { + return Path.join(...segments); +} + +/** + * Get all but the last segment of a path, often times the directory containing the path. All paths returned are normalized + */ +export function dirname(path: string) { + return Path.dirname(path); +} + +/** + * Convert a relative path to an absolute path based on the current working directory. All paths returned are normalized. + */ +export function resolve(path: string) { + return Path.isAbsolute(path) ? toNormal(path) : join(cwd, path); +} + +/** + * Returns true if the path is absolute, otherwise false + */ +export function isAbsolute(path: string) { + return Path.isAbsolute(path); +} + +/** + * Normalizes the passed path, ensuring that all path separators are unix-style `/` + */ +export function toNormal(path: string) { + return normalizePath(path); +} diff --git a/packages/kbn-type-summarizer-core/src/set_map.ts b/packages/kbn-type-summarizer-core/src/set_map.ts new file mode 100644 index 0000000000000..7ee4a2dd116af --- /dev/null +++ b/packages/kbn-type-summarizer-core/src/set_map.ts @@ -0,0 +1,49 @@ +/* + * 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. + */ + +/** + * A class for collecting items (V) based on some key (K) + */ +export class SetMap { + private sets = new Map>(); + + /** + * Is there a group for the `key`? + */ + has(key: K) { + return this.sets.has(key); + } + + /** + * Add a value to the group with `key`, if the group doesn't exist + * yet it is created. + */ + add(key: K, value: V) { + const set = this.sets.get(key); + if (set) { + set.add(value); + } else { + this.sets.set(key, new Set([value])); + } + } + + /** + * Get the group for the `key`, if the group doesn't exist then + * `undefined` is returned. + */ + get(key: K): Set | undefined { + return this.sets.get(key); + } + + /** + * Returns an iterator for the [K, V] entries stored in the SetMap + */ + values() { + return this.sets.values(); + } +} diff --git a/packages/kbn-type-summarizer-core/src/ts_helpers.ts b/packages/kbn-type-summarizer-core/src/ts_helpers.ts new file mode 100644 index 0000000000000..1397b5acb622a --- /dev/null +++ b/packages/kbn-type-summarizer-core/src/ts_helpers.ts @@ -0,0 +1,94 @@ +/* + * 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 ts from 'typescript'; +import Path from 'path'; +import { SetMap } from './set_map'; + +/** + * Does this AST node have a name which is an identifier? + */ +export function hasIdentifierName(node: any): node is { name: ts.Identifier } { + return typeof node === 'object' && node !== null && node.name && ts.isIdentifier(node.name); +} + +/** + * Is this symbol pointing to another symbol? + */ +export function isAliasSymbol(symbol: ts.Symbol) { + // eslint-disable-next-line no-bitwise + return Boolean(symbol.flags & ts.SymbolFlags.Alias); +} + +/** + * Get a human readable string describing a symbol, requires that symbols have a declaration + * which will be passed to describeNode() + */ +export function describeSymbol(symbol: ts.Symbol, cwd?: string) { + if (!symbol.declarations) { + return 'undeclared symbol'; + } + + return `Symbol(${describeNode(symbol.declarations[0], cwd)})`; +} + +function describeNodeLocation(node: ts.Node, cwd = process.cwd()) { + const sourceFile = node.getSourceFile(); + const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile, false)); + return `${Path.relative(cwd, sourceFile.fileName)}:${loc.line + 1}:${loc.character + 1}`; +} + +let syntaxMap: SetMap | undefined; +function getSyntaxMap() { + if (syntaxMap) { + return syntaxMap; + } + + syntaxMap = new SetMap(); + for (const [key, value] of Object.entries(ts.SyntaxKind)) { + if (typeof value === 'number') { + syntaxMap.add(value, key); + } + } + + return syntaxMap; +} + +/** + * Get a human readable name of the syntax "kind". TS nodes use enums for their "kind" field + * which makes it tricky to know what you're looking at, and the `ts.SyntaxKind` map is lossy + * because many enum members have the same numeric value. To get around this we convert the + * ts.SyntaxKind map into a `SetMap` which puts all the syntax kind names for a given number + * into a set and allows us to report all possible type names from `getKindName()` + */ +export function getKindName(node: ts.Node) { + const names = [...(getSyntaxMap().get(node.kind) ?? [])]; + + if (names.length === 1) { + return names[0]; + } else if (names.length > 1) { + const ors = names.slice(-1); + const last = names.at(-1); + return `${ors.join(', ')} or ${last}`; + } + + return 'unknown'; +} + +/** + * Turn a Node instance into a string which describes the type, name, filename, and position of the node + */ +export function describeNode(node: ts.Node, cwd?: string) { + const name = hasIdentifierName(node) + ? ` (${node.name.text})` + : ts.isIdentifier(node) + ? ` (${node.text})` + : ''; + + return `ts.${getKindName(node)}${name} @ ${describeNodeLocation(node, cwd)}`; +} diff --git a/packages/kbn-type-summarizer-core/tsconfig.json b/packages/kbn-type-summarizer-core/tsconfig.json new file mode 100644 index 0000000000000..789c6b3111115 --- /dev/null +++ b/packages/kbn-type-summarizer-core/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/packages/kbn-type-summarizer/BUILD.bazel b/packages/kbn-type-summarizer/BUILD.bazel index 8068c30a05d98..f3e344465d76f 100644 --- a/packages/kbn-type-summarizer/BUILD.bazel +++ b/packages/kbn-type-summarizer/BUILD.bazel @@ -1,14 +1,18 @@ load("@npm//@bazel/typescript:index.bzl", "ts_config") -load("@build_bazel_rules_nodejs//:index.bzl", "directory_file_path", "js_library", "nodejs_binary") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") -PKG_BASE_NAME = "kbn-type-summarizer" +PKG_DIRNAME = "kbn-type-summarizer" PKG_REQUIRE_NAME = "@kbn/type-summarizer" SOURCE_FILES = glob( [ "src/**/*.ts", - ] + ], + exclude = [ + "**/*.test.*", + "src/tests/**/*", + ], ) SRCS = SOURCE_FILES @@ -22,31 +26,51 @@ NPM_MODULE_EXTRA_FILES = [ "package.json", ] +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" RUNTIME_DEPS = [ - "@npm//@babel/runtime", - "@npm//@microsoft/api-extractor", - "@npm//source-map-support", - "@npm//chalk", - "@npm//getopts", "@npm//is-path-inside", "@npm//normalize-path", "@npm//source-map", + "@npm//strip-ansi", "@npm//tslib", + "//packages/kbn-type-summarizer-core", ] +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS TYPES_DEPS = [ - "@npm//@microsoft/api-extractor", "@npm//@types/jest", "@npm//@types/node", - "@npm//@types/normalize-path", - "@npm//getopts", "@npm//is-path-inside", - "@npm//normalize-path", + "@npm//@types/normalize-path", "@npm//source-map", "@npm//strip-ansi", "@npm//tslib", + "//packages/kbn-type-summarizer-core:npm_module_types", ] +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + ts_config( name = "tsconfig", src = "tsconfig.json", @@ -69,47 +93,22 @@ ts_project( tsconfig = ":tsconfig", ) -jsts_transpiler( - name = "target_node", - srcs = SRCS, - build_pkg_name = package_name(), -) - js_library( - name = PKG_BASE_NAME, + name = PKG_DIRNAME, srcs = NPM_MODULE_EXTRA_FILES, deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) -directory_file_path( - name = "bazel-cli-path", - directory = ":target_node", - path = "bazel_cli.js", -) - -nodejs_binary( - name = "bazel-cli", - data = [ - ":%s" % PKG_BASE_NAME - ], - entry_point = ":bazel-cli-path", - visibility = ["//visibility:public"], -) - pkg_npm( name = "npm_module", - deps = [ - ":%s" % PKG_BASE_NAME, - ], + deps = [":" + PKG_DIRNAME], ) filegroup( name = "build", - srcs = [ - ":npm_module", - ], + srcs = [":npm_module"], visibility = ["//visibility:public"], ) @@ -124,8 +123,6 @@ pkg_npm_types( filegroup( name = "build_types", - srcs = [ - ":npm_module_types", - ], + srcs = [":npm_module_types"], visibility = ["//visibility:public"], ) diff --git a/packages/kbn-type-summarizer/README.mdx b/packages/kbn-type-summarizer/README.mdx index bb92e110a4897..b9a2578eb6757 100644 --- a/packages/kbn-type-summarizer/README.mdx +++ b/packages/kbn-type-summarizer/README.mdx @@ -1,24 +1,32 @@ --- id: kibDevDocsOpsTypeSummarizer slug: /kibana-dev-docs/ops/type-summarizer -title: "@kbn/type-summarizer (BETA)" +title: "@kbn/type-summarizer" description: A tool to summarize and produce a single .d.ts file from tsc output supporting sourcemaps date: 2022-05-17 tags: ['kibana', 'dev', 'contributor', 'operations', 'type', 'summarizer', 'typescript', 'bundler', 'sourcemap'] --- -Consumes the .d.ts files for a package, produced by `tsc`, and generates a single `.d.ts` file of the public types along with a source map that points back to the original source. +Consumes the .d.ts files produced by `tsc` for a package and generates a single `.d.ts` file of the public types along with a source map that points back to the original source. ## You mean like API Extractor? Yeah, except with source map support and without all the legacy features and other features we disable to generate our current type summaries. -I first attempted to implement this in api-extractor but I (@spalger) hit a wall when dealing with the `Span` class. This class handles all the text output which ends up becoming source code, and I wasn't able to find a way to associate specific spans with source locations without getting 12 headaches. Instead I decided to try implementing this from scratch, reducing our reliance on the api-extractor project and putting us in control of how we generate type summaries. +We first attempted to implement this in api-extractor but we hit a wall when dealing with the `Span` class. This class handles all the text output which ends up becoming source code, and I wasn't able to find a way to associate specific spans with source locations without getting 12 headaches. Instead I decided to try implementing this from scratch, reducing our reliance on the api-extractor project and putting us in control of how we generate type summaries. -This package is missing some critical features for wider adoption, but rather than build the entire product in a branch I decided to implement support for a small number of TS features and put this to use in the `@kbn/crypto` module ASAP. +## Using the Type Summarizer -The plan is to expand to other packages in the Kibana repo, adding support for language features as we go. +The type-summarizer CLI from `@kbn/type-summarizer-cli` is automatically called by bazel via the `pkg_npm_types()` rule. If you want to run the CLI manually use `node scripts/type_summarizer ` and the type summary for your package will be written to `data/type_summarizer_output`. -## Something isn't working and I'm blocked! +## How does it work? -If there's a problem with the implementation blocking another team at any point we can move the package back to using api-extractor by removing the package from the `TYPE_SUMMARIZER_PACKAGES` list at the top of [packages/kbn-type-summarizer/src/lib/bazel_cli_config.ts](https://github.com/elastic/kibana/blob/main/packages/kbn-type-summarizer/src/lib/bazel_cli_config.ts). \ No newline at end of file +The type summarizer code is fairly well documented. The high level approach is to use two phases: + +### 1. Indexing +In this phase we traverse the symbol and AST graphs to determine the imports, local declarations, and ambient references needed to recreate the exported symbols of a package (along with all their references). This is done by the `AstIndexer` class and more information about the process is available there. + +### 2. Printing +After indexing is complete we use the created index to produce the necessary source code for the type summary file. This process is completed by the `printTypeSummary()` function in `src/lib/type_summary`, which uses the `SourceNode` class from the [source-map package](https://github.com/mozilla/source-map) to create the code and source maps of the type symmary file at the same time. + +The logic of this function is documented and should be relatively easy to follow once the structure of the `AstIndex` type is understood. \ No newline at end of file diff --git a/packages/kbn-type-summarizer/jest.config.js b/packages/kbn-type-summarizer/jest.config.js index 84b10626e82c8..76d7b935cfce7 100644 --- a/packages/kbn-type-summarizer/jest.config.js +++ b/packages/kbn-type-summarizer/jest.config.js @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -/** @typedef {import("@jest/types").Config.InitialOptions} JestConfig */ -/** @type {JestConfig} */ module.exports = { preset: '@kbn/test/jest_node', rootDir: '../..', diff --git a/packages/kbn-type-summarizer/jest.integration.config.js b/packages/kbn-type-summarizer/jest.integration.config.js index ae7b80073b935..50a3f64b6338d 100644 --- a/packages/kbn-type-summarizer/jest.integration.config.js +++ b/packages/kbn-type-summarizer/jest.integration.config.js @@ -6,8 +6,6 @@ * Side Public License, v 1. */ -/** @typedef {import("@jest/types").Config.InitialOptions} JestConfig */ -/** @type {JestConfig} */ module.exports = { preset: '@kbn/test/jest_integration_node', rootDir: '../..', diff --git a/packages/kbn-type-summarizer/package.json b/packages/kbn-type-summarizer/package.json index df9c5564d561e..b9118e2b43a3d 100644 --- a/packages/kbn-type-summarizer/package.json +++ b/packages/kbn-type-summarizer/package.json @@ -1,9 +1,9 @@ { "name": "@kbn/type-summarizer", + "private": true, "version": "1.0.0", - "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target_node/index.js", - "private": true, + "license": "SSPL-1.0 OR Elastic License 2.0", "kibana": { "devOnly": true } diff --git a/packages/kbn-type-summarizer/src/bazel_cli.ts b/packages/kbn-type-summarizer/src/bazel_cli.ts deleted file mode 100644 index 3d0da6fbc02b3..0000000000000 --- a/packages/kbn-type-summarizer/src/bazel_cli.ts +++ /dev/null @@ -1,82 +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 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 Fsp from 'fs/promises'; -import Path from 'path'; - -import { run } from './lib/run'; -import { parseBazelCliConfigs } from './lib/bazel_cli_config'; - -import { summarizePackage } from './summarize_package'; -import { runApiExtractor } from './run_api_extractor'; - -const HELP = ` -Script called from bazel to create the summarized version of a package. When called by Bazel -config is passed as a JSON encoded object. - -When called via "node scripts/type_summarizer" pass a path to a package and that -package's types will be read from node_modules and written to data/type-summarizer-output. - -`; - -run( - async ({ argv, log }) => { - log.debug('cwd:', process.cwd()); - log.debug('argv', process.argv); - - for (const config of parseBazelCliConfigs(argv)) { - try { - await Fsp.rm(config.outputDir, { recursive: true }); - } catch (error) { - if (error && error.code !== 'ENOENT') { - throw error; - } - } - - await Fsp.mkdir(config.outputDir, { recursive: true }); - - // generate pkg json output - await Fsp.writeFile( - Path.resolve(config.outputDir, 'package.json'), - JSON.stringify( - { - name: `@types/${config.packageName.replaceAll('@', '').replaceAll('/', '__')}`, - description: 'Generated by @kbn/type-summarizer', - types: './index.d.ts', - private: true, - license: 'MIT', - version: '1.1.0', - }, - null, - 2 - ) - ); - - if (config.type === 'type-summarizer') { - await summarizePackage(log, { - dtsDir: Path.dirname(config.inputPath), - inputPaths: [config.inputPath], - outputDir: config.outputDir, - tsconfigPath: config.tsconfigPath, - repoRelativePackageDir: config.repoRelativePackageDir, - }); - log.success('type summary created for', config.repoRelativePackageDir); - } else { - await runApiExtractor( - config.tsconfigPath, - config.inputPath, - Path.resolve(config.outputDir, 'index.d.ts') - ); - } - } - }, - { - helpText: HELP, - defaultLogLevel: 'quiet', - } -); diff --git a/packages/kbn-type-summarizer/src/index.ts b/packages/kbn-type-summarizer/src/index.ts index 1667ab5cd8d2f..d6589dbb49522 100644 --- a/packages/kbn-type-summarizer/src/index.ts +++ b/packages/kbn-type-summarizer/src/index.ts @@ -6,6 +6,5 @@ * Side Public License, v 1. */ -export type { Logger } from './lib/log'; export type { SummarizePacakgeOptions } from './summarize_package'; export { summarizePackage } from './summarize_package'; diff --git a/packages/kbn-type-summarizer/src/lib/ast_index.ts b/packages/kbn-type-summarizer/src/lib/ast_index.ts new file mode 100644 index 0000000000000..27269a91b3fb5 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ast_index.ts @@ -0,0 +1,145 @@ +/* + * 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 ts from 'typescript'; + +import { DecSymbol, ExportableDec } from './ts_nodes'; +import { ExportDetails } from './export_details'; +import { ImportDetails } from './import_details'; + +/** + * The AstIndex is produced by AstIndexer to represent all of the declarations/imports/refs that + * need to be used to produce the type summary. + */ +export interface AstIndex { + imports: ImportedDecs[]; + locals: LocalDecs[]; + ambientRefs: AmbientRef[]; +} + +/** + * ImportedDecs represent the declarations of a "root symbol" which is found in + * node_modules AND in imported in the source code. These will result in `import` + * and `export` statements in the type summary, based on if they are used locally + * or just re-exported. + */ +export interface ImportedDecs { + type: 'imported decs'; + + /** + * The root symbol which is imported from node_modules and used locally or exported + * by the indexed sourceFile + */ + readonly rootSymbol: DecSymbol; + + /** + * The ImportDetails describing if this is a named import, what name we are importing, + * etc. + */ + readonly details: ImportDetails; + + /** + * The count of local references to this rootSymbol, if this count is 0 and we are + * working with a default or named import, then we can simply re-export this imported + * symbol. + */ + readonly localUsageCount: number; + + /** + * An imported symbol can be exported more than once, so we support an array of export + * details here and ensure that each of these exports are recreated in the type summary + */ + exports: ExportDetails[]; +} + +/** + * AmbientRef objects describe references to root symbols which were never imported + * but are in the node_modules directory. These symbols might be placed there via + * `lib` or `types` configs in the ts project, but it doesn't really matter to us, + * we just need to know that this name is "reserved" and none of our declarations + * can re-use or override this name with different meaning. + */ +export interface AmbientRef { + type: 'ambient ref'; + + /** + * The root symbol which is referenced + */ + readonly rootSymbol: DecSymbol; + + /** + * The name that this root symbol is referenced by, which should be reserved + * for this ambient type in the type summary file. + */ + readonly name: string; +} + +/** + * LocalDecs represent the different rootSymbols which will be declared locally in + * the type summary. They are either declarations copied from the .d.ts files, or + * namespaces which are synthesized to represent imported namespaces. + */ +export type LocalDecs = CopiedDecs | NamespaceDec; + +/** + * A NamespaceDec represents a synthetic namepace which needs to be created in the + * type summary to power a namespace import in the source types. + */ +export interface NamespaceDec { + type: 'namespace dec'; + + /** + * The root symbol that points to the source file we will synthesize with this namespace + */ + readonly rootSymbol: DecSymbol; + + /** + * The sourceFile node which we will synthesize with this namespace, extracted + * from rootSymbol.declarations[0] for ease of access and so we can validate + * the shape of the symbol once. + */ + readonly sourceFile: ts.SourceFile; + + /** + * The members that the eventual namespace will need to have, and the rootSymbols that + * each member will reference/export from the namespace + */ + readonly members: Map; + + /** + * If this namespace is exported then this will be set to ExportDetails. We don't + * know if it is exported until all references to this specific rootSymbol are + * traversed, so `exported` can't be read only and is only defined at the end of indexing. + */ + exported: ExportDetails | undefined; +} + +/** + * CopiedDecs objects represent declarations for a "root symbol" which need to be + * copied into the resulting "type sumary" from the .d.ts files. + */ +export interface CopiedDecs { + type: 'copied decs'; + + /** + * The root symbol which is declared by the `decs` + */ + readonly rootSymbol: DecSymbol; + + /** + * The AST nodes which declare this root symbol + */ + readonly decs: ExportableDec[]; + + /** + * If these declarations are exported then this will be set to ExportDetails. We don't + * know if these decs are exported until all references to this specific rootSymbol are + * traversed, so `exported` can't be read only and is only defined at the end of indexing. + */ + exported: ExportDetails | undefined; +} diff --git a/packages/kbn-type-summarizer/src/lib/ast_indexer.ts b/packages/kbn-type-summarizer/src/lib/ast_indexer.ts new file mode 100644 index 0000000000000..ed27381454d7a --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ast_indexer.ts @@ -0,0 +1,403 @@ +/* + * 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 ts from 'typescript'; + +import { Logger, isAliasSymbol, describeSymbol, SetMap } from '@kbn/type-summarizer-core'; + +import { + AstIndex, + AmbientRef, + CopiedDecs, + ImportedDecs, + LocalDecs, + NamespaceDec, +} from './ast_index'; + +import { + toExportableDec, + assertDecSymbol, + DecSymbol, + toDecSymbol, + getSymbolDeclarations, +} from './ts_nodes'; +import { CounterMap } from './counter_map'; +import { SourceFileMapper } from './source_file_mapper'; +import { SymbolResolver } from './symbol_resolver'; +import { AstTraverser } from './ast_traverser'; +import { ImportDetails, getImportDetails } from './import_details'; +import { ExportDetails, getExportDetails } from './export_details'; + +/** + * The `AstIndexer` is responsible for collecting all the relevant information about the exports of + * a sourceFile from it's AST representation. + * + * The #indexExports method is the primary/only interface to use the AstIndexer and where the + * most useful documentation can be found. + */ +export class AstIndexer { + constructor( + private readonly typeChecker: ts.TypeChecker, + private readonly sources: SourceFileMapper, + private readonly symbols: SymbolResolver, + private readonly traverse: AstTraverser, + private readonly log: Logger + ) {} + + private getCopiedDecs(rootSymbol: DecSymbol): CopiedDecs | null { + const decs = rootSymbol.declarations.flatMap((dec) => { + // filter out declarations which are of no interest, traversing back to a type parameter or a + // function parameter, for instance, means this a symbol which is not of interest for this + // project so we ignore them + if (ts.isTypeParameterDeclaration(dec) || ts.isParameter(dec)) { + return []; + } + + if (this.sources.isExternal(dec)) { + return []; + } + + return toExportableDec(dec); + }); + + if (decs.length) { + return { + type: 'copied decs', + decs, + rootSymbol, + exported: undefined, + }; + } + + return null; + } + + private resolveExternalRootSymbol( + externalRootSymbol: DecSymbol, + aliases: ts.Symbol[], + exports: ExportDetails[] | undefined, + localUsageCount: number + ): ImportedDecs[] | AmbientRef { + const imports = aliases.flatMap((alias) => { + // traverse from the use to the symbol, stopping at the first import statement that points into the node_modules directory + let cursor = toDecSymbol(alias); + while (isAliasSymbol(cursor)) { + const next = this.typeChecker.getImmediateAliasedSymbol(cursor); + if (!next) { + break; + } + + // if any of the declarations of the next node are external then we have reched the stepping point that + // takes us from our local code to the node_modules. We won't traverse any more after we collect the + // import details of the declarations of cursoor + const nextIsExternal = getSymbolDeclarations(next).some((d) => this.sources.isExternal(d)); + + if (nextIsExternal) { + return cursor.declarations.flatMap((d) => getImportDetails(d) ?? []); + } + + cursor = toDecSymbol(next); + } + + return []; + }); + + if (!imports.length) { + return { + type: 'ambient ref', + rootSymbol: externalRootSymbol, + name: aliases[0].getName(), + }; + } + + const mergedImports: ImportDetails[] = []; + for (const id of imports) { + const existing = mergedImports.find( + (d) => + d.req === id.req && + d.type === id.type && + (d.type === 'named' && id.type === 'named' ? d.sourceName === id.sourceName : true) + ); + + if (existing) { + existing.typesOnly = existing.typesOnly && id.typesOnly; + } else { + mergedImports.push(id); + } + } + + if (mergedImports.length === 0) { + return []; + } + + return mergedImports.map( + (id): ImportedDecs => ({ + type: 'imported decs', + rootSymbol: externalRootSymbol, + details: id, + exports: exports || [], + localUsageCount, + }) + ); + } + + private findReferencedSymbols(root: ts.Node): ts.Symbol[] { + return [...this.traverse.findReferencedIdentifiers(root)].map((id) => + this.symbols.getForIdentifier(id) + ); + } + + /** + * This method determines all the relevant metadata about the exports of + * a specific `sourceFile` AST node. It indexes all the local declarations, imported + * declarations, and ambient refs that should end up in the public type + * summary file. + * + * To do this we use "symbols" from the `TypeChecker` provided by TypeScript. + * + * > "symbols" in the `TypeChecker` are not related to `Symbol`s in JS. + * + * Symbols describe a specific sourceFile/Type/Value in the source code, and allow + * us to understand the types referenced by specific AST nodes. For instance, we + * can ask the `TypeChecker` for the symbol of an `Identifier` node in the AST (the + * node type representing most named "keywords"; `a` and `foo` in `a(foo)` are both + * `Identifiers`). Every identifier in the source code should map to a specific symbol + * in the type system, which would be returned by the `TypeChecker`. These symbols + * then list the "declarations" which define/declare them. This is often a `class {}` + * or `interface {}` declaration but there are many types of declarations that could + * have defined this symbol. Additionally, the symbol may have multiple declarations + * if function overloads or interface extensions are used. + * + * Symbols can be "alias" symbols, indicating that they are declared in the source code + * but actually point to another declaration, either by variable assignment or via + * imports. When a type/value is imported from another file, the references to that + * type/value use alias symbols, which are declared by the import itself but point + * elsewhere. Thankfully, the `TypeChecker` has an API to traverse up the alias chain + * to the "root symbol". While indexing exports we regularly use the `SymbolResolver` + * to convert a symbol to it's `rootSymbol`, so that we can compare two references and + * determine if they are pointing to the same underlying declarations/type/value. + * + * To determine the full index of exports for a source file we start by asking the + * TypeChecker for the list of exported symbols of some sourceFile, then we traverse + * from those symbols to their declarations. If the symbol has declarations that are + * in node_modules, then either an `ImportedDecs` or `AmbientRef` object is added to + * the index, depending on wether the symbol is ever found to be imported. + * + * `ImportedDecs` describe how to import the declarations for that exported + * symbol in the type summary file. + * `AmbientRef` objects describe names that are expected to be declared ambiently, + * and therefore should be considered reserved in the type summary file. + * + * If any declarations for an exported symbol are local to the source code then they will + * result in a `LocalDecs` object being added to the index. Before adding a `LocalDecs` + * object to the index the AST of each local declaration is traversed to find + * references to other symbols. These references cause additional `ImportedDecs`, + * `AmbientRef`, or `LocalDecs` objects to be added to the index before the exported + * `LocalDecs`, ensuring that referenced declarations come first in the resulting type + * summary file and that all code referenced by the decalarations is included in the type + * summary file. + * + * Once all referenced declarations are found and added to the index the exported + * `LocalDecs` object is added to the index and the process is repeated for the next exported + * symbol. + * + * To ensure that we don't end up with duplicate declarations all `LocalDecs`, `ImportedDecs` + * and `AmbientRef` objects track the "root symbol" that they represent. Any time we + * encounter a new symbol which might need to be added to the index it is first resolved + * to it's root symbol to ensure we haven't already handled it. + */ + indexExports(sourceFile: ts.SourceFile): AstIndex { + return this.log.step('indexExports()', sourceFile.fileName, () => { + const sourceFileSymbol = this.typeChecker.getSymbolAtLocation(sourceFile); + if (!sourceFileSymbol) { + throw new Error(`symbol for source file not found: ${sourceFile.fileName}`); + } + + /** + * all alias symbols which point to a root symbol, which allows + * us to find all the import statements which point to an external + * rootSymbol + */ + const symbolAliases = new SetMap(); + /** + * counts the number of times a rootSymbol is used, this allows us + * to determine if an external rootSymbol needs to be imported for + * local usage, or just exported directly + */ + const rootSymbolLocalUses = new CounterMap(); + /** + * Map of the LocalDecs we have already created, allowing us to make + * sure that we only have a single LocalDecs instance for each rootSymbol + */ + const localDecsBySymbol = new Map(); + /** + * Set of all symbols we've indexed already, allowing us to freely call + * indexSymbol() with each referenced symbol and avoid duplicating work + */ + const indexedSymbols = new Set(); + /** + * Set of DecSymbols which are identified to have some external declarations + * that need to be imported in the final TypeSummary. These will be turned + * into ImportedDecs at the end once we have all the aliases indexed and + * can use the aliases to determine the import statements used to get these + * external symbols into the code. + */ + const externalSymbols = new Set(); + /** + * When we find a rootSymbol which is external, but there aren't any imports + * which pull in that symbol, then we track it here as a ref to an "ambient" + * type, like `Promise<>` from the TS lib. These refs don't end up in the type + * summary, but they do populate the list of UsedNames to ensure that we don't + * clobber those names with local declarations + */ + const ambientRefsByRootSymbol = new SetMap(); + /** + * These are the symbols which are exported from the `sourceFile` being indexed + * grouped by their rootSymbol. This allows us to get the export details for + * external symbols when we are creating ImportedDecs. + */ + const exportSymbolsByRootSymbols = new SetMap(); + /** + * The ordered array of LocalDecs, in the order which these decs should appear + * in the resulting type summary file. + */ + const localDecs: LocalDecs[] = []; + + /** + * This function is called to update the above state with the relevant details + * for a symbol we find as relevant to the exports of `sourceFile`. Calls itself + * with all the internal symbols referenced by the declarations of `symbol`. + */ + const indexSymbol = (symbol: ts.Symbol) => { + return this.log.verboseStep('indexSymbol()', symbol, () => { + if (indexedSymbols.has(symbol)) { + return; + } + indexedSymbols.add(symbol); + + const rootSymbol = this.symbols.toRootSymbol(symbol); + symbolAliases.add(rootSymbol, symbol); + + const existingLocalDec = localDecsBySymbol.get(rootSymbol); + if (!existingLocalDec) { + const [firstDec] = rootSymbol.declarations; + // when using a namespace import for a local module, the symbol resolves to the entire + // sourceFile imported, so we will index the sourceFile's exports and then track the + // namespace we need to synthesize in the output and maybe export + if ( + rootSymbol.declarations.length === 1 && + ts.isSourceFile(firstDec) && + !this.sources.isExternal(firstDec) + ) { + const exports = this.typeChecker.getExportsOfModule(rootSymbol); + const ns: NamespaceDec = { + type: 'namespace dec', + rootSymbol, + exported: undefined, + members: new Map( + exports.map((s) => [s.name, this.symbols.toRootSymbol(s)]) + ), + sourceFile: firstDec, + }; + localDecsBySymbol.set(rootSymbol, ns); + + for (const s of exports) { + indexSymbol(s); + } + localDecs.push(ns); + return; + } + + const locals = this.getCopiedDecs(rootSymbol); + localDecsBySymbol.set(rootSymbol, locals); + + if (locals) { + for (const dec of locals.decs) { + for (const refSymbol of this.findReferencedSymbols(dec)) { + const refRoot = this.symbols.toRootSymbol(refSymbol); + rootSymbolLocalUses.incr(refRoot); + indexSymbol(refSymbol); + } + } + + localDecs.push(locals); + } + } + + if (rootSymbol.declarations.some((d) => this.sources.isExternal(d))) { + externalSymbols.add(rootSymbol); + } + }); + }; + + // iterate through the direct exports of `sourceFile` and index them + for (const exportSymbol of this.typeChecker.getExportsOfModule(sourceFileSymbol)) { + // convert `symbol` to a DecSymbol + assertDecSymbol(exportSymbol); + + // mutate the state to know about this symbol + indexSymbol(exportSymbol); + + // resolve to the rootSymbol that is being exported + const rootSymbol = this.symbols.toRootSymbol(exportSymbol); + + // list this as an exported symbol for when we're trying to define export info for imports + exportSymbolsByRootSymbols.add(rootSymbol, exportSymbol); + + // ensure that if LocalDecs are created for this symbol they have the necessary ExportDetails + const local = localDecsBySymbol.get(rootSymbol); + if (local) { + local.exported = getExportDetails(this.typeChecker, exportSymbol); + } + } + + // convert the externalSymbols to ImportDecs and AmbientRefs based on whether they are imported or not + const importedDecs = [...externalSymbols].flatMap((rootSymbol) => { + const aliases = symbolAliases.get(rootSymbol); + if (!aliases) { + throw new Error(`external symbol has no aliases somehow ${describeSymbol(rootSymbol)}`); + } + + const exportSymbols = exportSymbolsByRootSymbols.get(rootSymbol); + + const resolved = + this.resolveExternalRootSymbol( + rootSymbol, + [...aliases], + exportSymbols + ? [...exportSymbols].map((s) => getExportDetails(this.typeChecker, s)) + : undefined, + rootSymbolLocalUses.get(rootSymbol) + ) ?? []; + + if (Array.isArray(resolved)) { + return resolved; + } + + ambientRefsByRootSymbol.add(resolved.rootSymbol, resolved); + return []; + }); + + return { + imports: importedDecs, + locals: localDecs, + ambientRefs: [...ambientRefsByRootSymbol.values()].flatMap((group) => { + const names = new Set(); + return [...group].filter((g) => { + if (names.has(g.name)) { + return false; + } + + names.add(g.name); + return true; + }); + }), + }; + }); + } +} diff --git a/packages/kbn-type-summarizer/src/lib/ast_traverser.ts b/packages/kbn-type-summarizer/src/lib/ast_traverser.ts new file mode 100644 index 0000000000000..64cb98db7eda0 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ast_traverser.ts @@ -0,0 +1,162 @@ +/* + * 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 ts from 'typescript'; + +import { hasIdentifierName, describeNode, Logger } from '@kbn/type-summarizer-core'; +import { isExportableDec } from './ts_nodes'; +import { SymbolResolver } from './symbol_resolver'; +import { SourceFileMapper } from './source_file_mapper'; + +/** + * This object is responsible for exposing utilities for traversing the AST nodes + * to find relevant identifiers within. + */ +export class AstTraverser { + constructor( + private readonly symbols: SymbolResolver, + private readonly sources: SourceFileMapper, + private readonly log: Logger + ) {} + + private doesIdentifierResolveToLocalDeclaration(id: ts.Identifier) { + const rootSymbol = this.symbols.toRootSymbol(this.symbols.getForIdentifier(id)); + for (const dec of rootSymbol.declarations) { + if (!this.sources.isExternal(dec)) { + return true; + } + } + + return false; + } + + /** + * Traverses through the children of `root` deeply to find all identifiers which are + * references. Ignores idenfiers which are names of structres (like names of properties, + * arguments, function declarations, etc) as well as a few other identifiers which we + * are pretty sure never could point to a reference outside of this node. + * + * The goal here is to find all identifiers which we can then convert into symbols to + * find all the types/values that are referenced by the passed `root` AST node. + */ + findReferencedIdentifiers(root: ts.Node): Set { + return this.log.verboseStep('traverse.findReferencedIdentifiers()', root, () => { + const queue = new Set([root]); + const identifiers = new Set(); + + for (const node of queue) { + // ImportTypeNode's are inline `import('...').Type` nodes which TS often injects for inferred return types + // often time these return types are for identifiers from node_modules which we will maintain, since the + // node modules will be available for the summary. If the imported symbol resolves to local code though + // we need to grab the referenced identifier and replace the whole ImportTypeNode with a local reference + // after the declarations for that symbol are included in the summary + if (ts.isImportTypeNode(node)) { + // iterate the type arguments of ImportTypeNode + if (node.typeArguments) { + for (const arg of node.typeArguments) { + queue.add(arg); + } + } + + if (node.qualifier) { + // if the qualifier resolves to a local declaration then count it as an identifier, later + // on we replace the parent node of identifiers inside or `ImportTypeNode`s + if (ts.isIdentifier(node.qualifier)) { + if (this.doesIdentifierResolveToLocalDeclaration(node.qualifier)) { + identifiers.add(node.qualifier); + } + continue; + } + + if (ts.isQualifiedName(node.qualifier) && ts.isIdentifier(node.qualifier.left)) { + if (this.doesIdentifierResolveToLocalDeclaration(node.qualifier.left)) { + identifiers.add(node.qualifier.left); + } + continue; + } + } + + throw new Error( + `unable to find relevant identifier in ImportTypeNode.qualifier ${describeNode(node)}` + ); + } + + const ignores: ts.Node[] = []; + + // ignore parameter/property names, names aren't ever references to other declarations AFAIK + if (hasIdentifierName(node)) { + ignores.push(node.name); + } + + // ignore the source name of destructured params ie. X in `function foo({ X: Foo }: Type)` + if (ts.isBindingElement(node) && node.propertyName) { + ignores.push(node.propertyName); + } + + // ignore parameter names in type predicates ie. X in `(foo: any): X is Bar` + if (ts.isTypePredicateNode(node)) { + ignores.push(node.parameterName); + } + + // ignore identifiers in "QualifiedName" nodes, which are found in TypeReferences like + // `semver.SemVer`, we don't need to treat `SemVer` as a ref because we're capturing `semver` + if (ts.isQualifiedName(node) && ts.isIdentifier(node.right)) { + ignores.push(node.right); + } + + node.forEachChild((child) => { + if (ignores.includes(child)) { + return; + } + + if (ts.isIdentifier(child)) { + identifiers.add(child); + } else { + queue.add(child); + } + }); + } + + return identifiers; + }); + } + + /** + * Returns "structural" identifiers for a `root` node, which includes the name of the `root` and + * the name of any "members", like the names of properties in an interface or class, the name of + * options in an enum, all so we can identify their posistions later on and make sure they reference + * their source location in the source maps. + */ + findStructuralIdentifiers(root: ts.Node): Set { + return this.log.verboseStep('traverse.findStructuralIdentifiers()', root, () => { + const queue = new Set([root]); + const identifiers = new Set(); + + for (const node of queue) { + if (isExportableDec(node)) { + identifiers.add(node.name); + } + + if ( + ts.isClassDeclaration(node) || + ts.isInterfaceDeclaration(node) || + ts.isTypeLiteralNode(node) || + ts.isEnumDeclaration(node) + ) { + for (const member of node.members) { + if (hasIdentifierName(member)) { + identifiers.add(member.name); + } + } + } + } + + return identifiers; + }); + } +} diff --git a/packages/kbn-type-summarizer/src/lib/counter_map.ts b/packages/kbn-type-summarizer/src/lib/counter_map.ts new file mode 100644 index 0000000000000..afb5c95723a1d --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/counter_map.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * A simple Map wrapper which counts the number of times a value `K` is + * passed to `incr()`. + */ +export class CounterMap { + private counters = new Map(); + + incr(key: K, by: number = 1) { + this.counters.set(key, (this.counters.get(key) ?? 0) + by); + } + + get(key: K): number { + return this.counters.get(key) ?? 0; + } +} diff --git a/packages/kbn-type-summarizer/src/lib/dts_snipper.ts b/packages/kbn-type-summarizer/src/lib/dts_snipper.ts new file mode 100644 index 0000000000000..02f25095fcdf1 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/dts_snipper.ts @@ -0,0 +1,257 @@ +/* + * 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 ts from 'typescript'; + +import { describeNode, Logger } from '@kbn/type-summarizer-core'; +import { DecSymbol } from './ts_nodes'; +import { AstTraverser } from './ast_traverser'; +import { SymbolResolver } from './symbol_resolver'; + +const COMMENT_TRIM = /^(\s+)(\/\*|\*|\/\/)/; + +/** + * A Snippet which represents an arbitrary bit of source code. The `value` + * of these snippets will be included verbatim in the type summary output + */ +export interface SourceSnippet { + type: 'source'; + value: string; +} + +/** + * A Snippet which represents an existing `export` modifier, or the location where + * one should exist if it needs to exist. When printing the `Snippet`s to + * the type summary file we will determine if the structure bring printed + * should be inline-exported, and if so replace this snippet with the + * relevant export/export-type keyword(s). If the structure shouldn't be + * exported inline then we will simply ignore this snippet. + */ +export interface ExportSnippet { + type: 'export'; + noExportRequiresDeclare: boolean; +} + +/** + * A Snippet which represents an identifier in the source. These snippets will + * be replaced with `SourceNode` objects in the type summary output so that + * the `.map` file can be generated which maps the identifiers to their original + * source location. + */ +export interface IdentifierSnippet { + type: 'indentifier'; + rootSymbol: DecSymbol; + identifier: ts.Identifier; + text: string; + structural: boolean; +} + +export type Snippet = SourceSnippet | ExportSnippet | IdentifierSnippet; + +/** + * The DtsSnipper is responsible for taking the source text of an AST node + * and converting it into an array of `Snippet` objects, which allow us to + * reuse the code structure from `.d.ts` produced by TS but replace specific + * snippets of that text with different values or `SourceNode`s from the + * `source-map` library which allows is how we produce source-maps for our + * type summary. + */ +export class DtsSnipper { + constructor( + private readonly traverse: AstTraverser, + private readonly symbols: SymbolResolver, + private readonly log: Logger + ) {} + + private getVariableDeclarationList(node: ts.VariableDeclaration) { + const list = node.parent; + if (!ts.isVariableDeclarationList(list)) { + throw new Error( + `expected parent of variable declaration to be a VariableDeclarationList ${describeNode( + list + )}` + ); + } + return list; + } + + private getSourceWithLeadingComments(node: ts.Node) { + // variable declarations regularly have leading comments but they're two-parents up, so we have to handle them separately + if (!ts.isVariableDeclaration(node)) { + return node.getFullText(); + } + + const list = this.getVariableDeclarationList(node); + if (list.declarations.length > 1) { + return node.getFullText(); + } + + const statement = list.parent; + if (!ts.isVariableStatement(statement)) { + throw new Error('expected parent of VariableDeclarationList to be a VariableStatement'); + } + + return statement.getFullText(); + } + + private getLeadingComments(node: ts.Node): Snippet[] { + const fullText = this.getSourceWithLeadingComments(node); + const ranges = ts.getLeadingCommentRanges(fullText, 0); + if (!ranges) { + return []; + } + + return ranges.flatMap((range) => { + const comment = fullText + .slice(range.pos, range.end) + .split('\n') + .map((line) => { + const match = line.match(COMMENT_TRIM); + if (!match) { + return line; + } + + const [, spaces, type] = match; + return line.slice(type === '*' ? spaces.length - 1 : spaces.length); + }) + .map((line) => `${line}`) + .join('\n'); + + if (comment.startsWith('/// { + const snippets: Snippet[] = this.getLeadingComments(root); + + const getIdStart = (id: ts.Identifier) => + ts.isImportTypeNode(id.parent) + ? id.parent.getStart() + : ts.isQualifiedName(id.parent) && ts.isImportTypeNode(id.parent.parent) + ? id.parent.parent.getStart() + : id.getStart(); + const getIdEnd = (id: ts.Identifier) => + ts.isImportTypeNode(id.parent) + ? id.parent.getEnd() + : ts.isQualifiedName(id.parent) && ts.isImportTypeNode(id.parent.parent) + ? id.parent.parent.getEnd() + : id.getEnd(); + + const structural = this.traverse.findStructuralIdentifiers(root); + const identifiers = Array.from( + new Set([...structural, ...this.traverse.findReferencedIdentifiers(root)]) + ).sort((a, b) => getIdStart(a) - getIdStart(b)); + + const source = root.getText(); + const sourceStart = root.getStart(); + const sourceEnd = sourceStart + source.length; + let cursor = sourceStart; + + // if there is text from the source between our current position and some other position + // then copy it into the result and update our current position to that position + const maybeSlurpTo = (until: number) => { + if (cursor < until) { + snippets.push({ + type: 'source', + value: source.slice(cursor - sourceStart, until - sourceStart), + }); + cursor = until; + } + }; + + // Either replace the existing export with a placeholder, or inject an export placeholder before + // the root nodes own text so we know where to put the export if needed + const exportMod = root.modifiers?.find((m) => m.kind === ts.SyntaxKind.ExportKeyword); + // when TS prints a function declaration to the .d.ts file with an `export` keyword, it doesn't need to be + // `declare`d, so it sometimes skips it. If we end up striping the `export` keyword we need to put `declare` + // in it's place so that the `.d.ts` syntax is valid + const noExportRequiresDeclare = + (ts.isFunctionDeclaration(root) || ts.isClassDeclaration(root)) && + !root.modifiers?.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword); + + if (exportMod) { + const modStart = exportMod.getStart(); + const modEnd = exportMod.getEnd(); + maybeSlurpTo(modStart); + + snippets.push({ + type: 'export', + noExportRequiresDeclare, + }); + + // export is always followed by a space, so skip the space too + cursor = modEnd + 1; + } else { + const rootStart = root.getStart(); + maybeSlurpTo(rootStart); + + snippets.push({ + type: 'export', + noExportRequiresDeclare, + }); + cursor = rootStart; + } + + // inject a `const`, `let`, or `var` before variable declarations + if (ts.isVariableDeclaration(root) && ts.isVariableDeclarationList(root.parent)) { + // eslint-disable-next-line no-bitwise + if (root.parent.flags & ts.NodeFlags.Const) { + snippets.push({ + type: 'source', + value: 'declare const ', + }); + // eslint-disable-next-line no-bitwise + } else if (root.parent.flags & ts.NodeFlags.Let) { + snippets.push({ + type: 'source', + value: 'declare let ', + }); + } else { + snippets.push({ + type: 'source', + value: 'declare var ', + }); + } + } + + for (const identifier of identifiers) { + const start = getIdStart(identifier); + const end = getIdEnd(identifier); + maybeSlurpTo(start); + + snippets.push({ + type: 'indentifier', + identifier, + text: identifier.getText(), + rootSymbol: this.symbols.toRootSymbol( + this.symbols.getForIdentifier(identifier), + identifier + ), + structural: structural.has(identifier), + }); + + cursor = end; + } + + maybeSlurpTo(sourceEnd); + + return snippets; + }); + } +} diff --git a/packages/kbn-type-summarizer/src/lib/export_collector/collector_results.ts b/packages/kbn-type-summarizer/src/lib/export_collector/collector_results.ts deleted file mode 100644 index abcdf7e6f7b0a..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/export_collector/collector_results.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 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 * as ts from 'typescript'; -import { ValueNode, DecSymbol } from '../ts_nodes'; -import { ResultValue } from './result_value'; -import { ImportedSymbol } from './imported_symbol'; -import { Reference, ReferenceKey } from './reference'; -import { SourceMapper } from '../source_mapper'; -import { ExportInfo } from '../export_info'; - -export type CollectorResult = - | Reference - | { type: 'imports'; imports: ImportedSymbol[] } - | ResultValue; - -export class CollectorResults { - importedSymbols = new Set(); - - nodes: ResultValue[] = []; - nodesByAst = new Map(); - - constructor(private readonly sourceMapper: SourceMapper) {} - - addNode(exportInfo: ExportInfo | undefined, node: ValueNode) { - const existing = this.nodesByAst.get(node); - if (existing) { - existing.exportInfo ||= exportInfo; - return; - } - - const result = new ResultValue(exportInfo, node); - this.nodesByAst.set(node, result); - this.nodes.push(result); - } - - addImportFromNodeModules( - exportInfo: ExportInfo | undefined, - sourceSymbol: DecSymbol, - importSymbol: DecSymbol, - moduleId: string - ) { - const imp = ImportedSymbol.fromSymbol(sourceSymbol, importSymbol, moduleId); - imp.exportInfo ||= exportInfo; - this.importedSymbols.add(imp); - } - - private getReferencesFromNodes() { - // collect the references from all the sourcefiles of all the resulting nodes - const sourceFiles = new Set(); - for (const { node } of this.nodes) { - sourceFiles.add(this.sourceMapper.getSourceFile(node)); - } - - const references: Record> = { - lib: new Set(), - types: new Set(), - }; - for (const sourceFile of sourceFiles) { - for (const ref of sourceFile.libReferenceDirectives) { - references.lib.add(ref.fileName); - } - for (const ref of sourceFile.typeReferenceDirectives) { - references.types.add(ref.fileName); - } - } - - return [ - ...Array.from(references.lib).map((name) => new Reference('lib', name)), - ...Array.from(references.types).map((name) => new Reference('types', name)), - ]; - } - - getAll(): CollectorResult[] { - return [ - ...this.getReferencesFromNodes(), - { type: 'imports', imports: Array.from(this.importedSymbols) }, - ...this.nodes, - ]; - } -} diff --git a/packages/kbn-type-summarizer/src/lib/export_collector/exports_collector.ts b/packages/kbn-type-summarizer/src/lib/export_collector/exports_collector.ts deleted file mode 100644 index 65ee07d9716fb..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/export_collector/exports_collector.ts +++ /dev/null @@ -1,227 +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 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 * as ts from 'typescript'; - -import { Logger } from '../log'; -import { - assertExportedValueNode, - isExportedValueNode, - DecSymbol, - assertDecSymbol, - toDecSymbol, - ExportFromDeclaration, - isExportFromDeclaration, - isAliasSymbol, -} from '../ts_nodes'; - -import { ExportInfo } from '../export_info'; -import { CollectorResults } from './collector_results'; -import { SourceMapper } from '../source_mapper'; -import { isNodeModule } from '../is_node_module'; - -interface ResolvedNmImport { - type: 'import_from_node_modules'; - importSymbol: DecSymbol; - sourceSymbol: DecSymbol; - moduleId: string; -} -interface ResolvedSymbol { - type: 'symbol'; - symbol: DecSymbol; -} - -export class ExportCollector { - constructor( - private readonly log: Logger, - private readonly typeChecker: ts.TypeChecker, - private readonly sourceFile: ts.SourceFile, - private readonly dtsDir: string, - private readonly sourceMapper: SourceMapper - ) {} - - private getParentImport( - symbol: DecSymbol - ): ts.ImportDeclaration | ExportFromDeclaration | undefined { - for (const node of symbol.declarations) { - let cursor: ts.Node = node; - while (true) { - if (ts.isImportDeclaration(cursor) || isExportFromDeclaration(cursor)) { - return cursor; - } - - if (ts.isSourceFile(cursor)) { - break; - } - - cursor = cursor.parent; - } - } - } - - private getAllChildSymbols( - node: ts.Node, - results = new Set(), - seen = new Set() - ) { - node.forEachChild((child) => { - const childSymbol = this.typeChecker.getSymbolAtLocation(child); - if (childSymbol) { - results.add(toDecSymbol(childSymbol)); - } - if (!seen.has(child)) { - seen.add(child); - this.getAllChildSymbols(child, results, seen); - } - }); - return results; - } - - private resolveAliasSymbolStep(alias: ts.Symbol): DecSymbol { - // get the symbol this symbol references - const aliased = this.typeChecker.getImmediateAliasedSymbol(alias); - if (!aliased) { - throw new Error(`symbol [${alias.escapedName}] is an alias without aliased symbol`); - } - assertDecSymbol(aliased); - return aliased; - } - - private getImportFromNodeModules(symbol: DecSymbol): undefined | ResolvedNmImport { - const parentImport = this.getParentImport(symbol); - if (parentImport && ts.isStringLiteral(parentImport.moduleSpecifier)) { - // this symbol is within an import statement, is it an import from a node_module? first - // we resolve the import alias to it's source symbol, which we will show us the file that - // the import resolves to - const aliased = this.resolveAliasSymbolStep(symbol); - const targetPaths = [ - ...new Set(aliased.declarations.map((d) => this.sourceMapper.getSourceFile(d).fileName)), - ]; - - let nmCount = 0; - let localCount = 0; - for (const targetPath of targetPaths) { - if (isNodeModule(this.dtsDir, targetPath)) { - nmCount += 1; - } else { - localCount += 1; - } - } - - if (nmCount === targetPaths.length) { - return { - type: 'import_from_node_modules', - importSymbol: symbol, - sourceSymbol: aliased, - moduleId: parentImport.moduleSpecifier.text, - }; - } - - if (localCount === targetPaths.length) { - return undefined; - } - - throw new Error('using a symbol which is locally extended is unsupported at this time'); - } - } - - private resolveAliasSymbol(alias: DecSymbol): ResolvedNmImport | ResolvedSymbol { - let symbol = alias; - - while (isAliasSymbol(symbol)) { - const nmImport = this.getImportFromNodeModules(symbol); - if (nmImport) { - return nmImport; - } - - symbol = this.resolveAliasSymbolStep(symbol); - } - - return { - type: 'symbol', - symbol, - }; - } - - private traversedSymbols = new Set(); - private collectResults( - results: CollectorResults, - exportInfo: ExportInfo | undefined, - symbol: DecSymbol - ): void { - const seen = this.traversedSymbols.has(symbol); - if (seen && !exportInfo) { - return; - } - this.traversedSymbols.add(symbol); - - const source = this.resolveAliasSymbol(symbol); - if (source.type === 'import_from_node_modules') { - results.addImportFromNodeModules( - exportInfo, - source.sourceSymbol, - source.importSymbol, - source.moduleId - ); - return; - } - - symbol = source.symbol; - if (seen) { - for (const node of symbol.declarations) { - assertExportedValueNode(node); - results.addNode(exportInfo, node); - } - return; - } - - const globalDecs: ts.Declaration[] = []; - const localDecs: ts.Declaration[] = []; - for (const node of symbol.declarations) { - const sourceFile = this.sourceMapper.getSourceFile(node); - (isNodeModule(this.dtsDir, sourceFile.fileName) ? globalDecs : localDecs).push(node); - } - - if (globalDecs.length) { - this.log.debug( - `Ignoring ${globalDecs.length} global declarations for "${source.symbol.escapedName}"` - ); - } - - for (const node of localDecs) { - // iterate through the child nodes to find nodes we need to export to make this useful - const childSymbols = this.getAllChildSymbols(node); - childSymbols.delete(symbol); - - for (const childSymbol of childSymbols) { - this.collectResults(results, undefined, childSymbol); - } - - if (isExportedValueNode(node)) { - results.addNode(exportInfo, node); - } - } - } - - run(): CollectorResults { - const results = new CollectorResults(this.sourceMapper); - - const moduleSymbol = this.typeChecker.getSymbolAtLocation(this.sourceFile); - if (!moduleSymbol) { - this.log.warn('Source file has no symbol in the type checker, is it empty?'); - return results; - } - - for (const symbol of this.typeChecker.getExportsOfModule(moduleSymbol)) { - assertDecSymbol(symbol); - this.collectResults(results, ExportInfo.fromSymbol(symbol), symbol); - } - - return results; - } -} diff --git a/packages/kbn-type-summarizer/src/lib/export_collector/imported_symbol.ts b/packages/kbn-type-summarizer/src/lib/export_collector/imported_symbol.ts deleted file mode 100644 index 0ec05e98e4568..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/export_collector/imported_symbol.ts +++ /dev/null @@ -1,68 +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 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 * as ts from 'typescript'; -import { DecSymbol, findKind } from '../ts_nodes'; -import { ExportInfo } from '../export_info'; - -const cache = new WeakMap(); - -export class ImportedSymbol { - static fromSymbol(source: DecSymbol, importSymbol: DecSymbol, moduleId: string) { - const cached = cache.get(source); - if (cached) { - return cached; - } - - if (importSymbol.declarations.length !== 1) { - throw new Error('expected import symbol to have exactly one declaration'); - } - - const dec = importSymbol.declarations[0]; - if ( - !ts.isImportClause(dec) && - !ts.isExportSpecifier(dec) && - !ts.isImportSpecifier(dec) && - !ts.isNamespaceImport(dec) - ) { - const kind = findKind(dec); - throw new Error( - `expected import declaration to be an ImportClause, ImportSpecifier, or NamespaceImport, got ${kind}` - ); - } - - if (!dec.name) { - throw new Error(`expected ${findKind(dec)} to have a name defined`); - } - - const imp = ts.isImportClause(dec) - ? new ImportedSymbol(importSymbol, 'default', dec.name.text, dec.isTypeOnly, moduleId) - : ts.isNamespaceImport(dec) - ? new ImportedSymbol(importSymbol, '*', dec.name.text, dec.parent.isTypeOnly, moduleId) - : new ImportedSymbol( - importSymbol, - dec.name.text, - dec.propertyName?.text, - dec.isTypeOnly || dec.parent.parent.isTypeOnly, - moduleId - ); - - cache.set(source, imp); - return imp; - } - - public exportInfo: ExportInfo | undefined; - - constructor( - public readonly symbol: DecSymbol, - public readonly remoteName: string, - public readonly localName: string | undefined, - public readonly isTypeOnly: boolean, - public readonly moduleId: string - ) {} -} diff --git a/packages/kbn-type-summarizer/src/lib/export_details.ts b/packages/kbn-type-summarizer/src/lib/export_details.ts new file mode 100644 index 0000000000000..6a805e5707fdd --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/export_details.ts @@ -0,0 +1,83 @@ +/* + * 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 * as ts from 'typescript'; + +import { describeNode, isAliasSymbol } from '@kbn/type-summarizer-core'; +import { isExportableDec, getSymbolDeclarations } from './ts_nodes'; + +export type ExportDetails = NamedExportDetails | DefaultExportDetails; + +export interface DefaultExportDetails { + type: 'default'; +} + +export interface NamedExportDetails { + type: 'named'; + typeOnly: boolean; + name: string; +} + +/** + * Determine the export name of an export symbol + */ +function getExportName(node: ts.Node): string { + if (ts.isExportSpecifier(node) || isExportableDec(node)) { + return node.name.text; + } + + throw new Error(`unsure how to get export name from ${describeNode(node)}`); +} + +/** + * Determine if an export symbol was type-only exported. This would be true if the top-level export + * statement is type only, or if there are other export statements up the alias chain where the + * parent symbol was type-only exported. + */ +function isTypeOnlyExported(typeChecker: ts.TypeChecker, exportSymbol: ts.Symbol) { + if (getSymbolDeclarations(exportSymbol).some((e) => ts.isTypeOnlyImportOrExportDeclaration(e))) { + return true; + } + + if (isAliasSymbol(exportSymbol)) { + const next = typeChecker.getImmediateAliasedSymbol(exportSymbol); + if (next && isTypeOnlyExported(typeChecker, next)) { + return true; + } + } + return false; +} + +/** + * Given an exported symbol, determine details about the export from the symbols declarations + * including if it was a type-only export, a default export, or a named export and the name + * it was exported with + */ +export function getExportDetails( + typeChecker: ts.TypeChecker, + exportSymbol: ts.Symbol +): ExportDetails { + if (!exportSymbol.declarations?.length) { + throw new Error('unable to get export details for symbols without any declarations'); + } + + if ( + exportSymbol.declarations.length === 1 && + ts.isExportAssignment(exportSymbol.declarations[0]) + ) { + return { + type: 'default', + }; + } + + return { + type: 'named', + typeOnly: isTypeOnlyExported(typeChecker, exportSymbol), + name: getExportName(exportSymbol.declarations[0]), + }; +} diff --git a/packages/kbn-type-summarizer/src/lib/export_info.ts b/packages/kbn-type-summarizer/src/lib/export_info.ts deleted file mode 100644 index a1b867034d2a6..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/export_info.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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 * as ts from 'typescript'; -import { DecSymbol } from './ts_nodes'; - -type ExportType = 'export' | 'export type'; - -export class ExportInfo { - static fromSymbol(symbol: DecSymbol) { - const exportInfo = symbol.declarations.reduce((acc: ExportInfo | undefined, dec) => { - const next = ExportInfo.fromNode(dec, symbol); - if (!acc) { - return next; - } - - if (next.name !== acc.name || next.type !== acc.type) { - throw new Error('unable to handle export symbol with different types of declarations'); - } - - return acc; - }, undefined); - - if (!exportInfo) { - throw new Error('unable to get candidates'); - } - - return exportInfo; - } - - static fromNode(node: ts.Node, symbol: DecSymbol) { - let type: ExportType = 'export'; - - if (ts.isExportSpecifier(node)) { - if (node.isTypeOnly || node.parent.parent.isTypeOnly) { - type = 'export type'; - } - } - - let name; - if ((ts.isFunctionDeclaration(node) || ts.isExportSpecifier(node)) && node.name) { - name = node.name.getText(); - } - - return new ExportInfo(name ?? node.getText(), type, symbol); - } - - constructor( - public readonly name: string, - public readonly type: ExportType, - public readonly symbol: DecSymbol - ) {} -} diff --git a/packages/kbn-type-summarizer/src/lib/import_details.ts b/packages/kbn-type-summarizer/src/lib/import_details.ts new file mode 100644 index 0000000000000..ca68c79fe6a58 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/import_details.ts @@ -0,0 +1,135 @@ +/* + * 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 * as ts from 'typescript'; + +import { describeNode } from '@kbn/type-summarizer-core'; + +interface BaseImportDetails { + typesOnly: boolean; + req: string; + node: T; +} + +/** + * Indicates that the import was using `import * as X from 'y'` syntax + */ +export interface NamespaceImportDetails + extends BaseImportDetails { + type: 'namespace'; +} + +/** + * Indicates that the import was using `import X from 'y'` syntax + */ +export interface DefaultImportDetails extends BaseImportDetails { + type: 'default'; +} + +/** + * Indicates that the import was using `import { X } from 'y'` syntax, along + * with the name of the imported value from the source module. + */ +export interface NamedImportDetails + extends BaseImportDetails { + type: 'named'; + sourceName: string; +} + +/** + * The different types of ImportDetails that can be returned from `getImportDetails()` + */ +export type ImportDetails = NamespaceImportDetails | DefaultImportDetails | NamedImportDetails; + +/** + * Type guard for nodes which have a module specifier + */ +function hasModuleSpecifier( + node: T +): node is T & { moduleSpecifier: ts.StringLiteral } { + return !!(node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)); +} + +/** + * Determine the module request from a node which might have one, otherwise throw + */ +function getReq(node: ts.ImportDeclaration | ts.ExportDeclaration) { + if (hasModuleSpecifier(node)) { + return node.moduleSpecifier.text; + } + + throw new Error( + `syntax error, module specifier should be a string literal ${describeNode(node)}` + ); +} + +/** + * Given any node, determine if it represents a node that is related to an import statement + * and determine the details about that import, including type, req, source name (for named imports) + * and if the import is type-only. + * + * This also works to get the "import" details from `export ... from '...'` statements. + */ +export function getImportDetails(node: ts.Node): ImportDetails | undefined { + // import { bar } from './bar' + if (ts.isImportSpecifier(node)) { + return { + type: 'named', + typesOnly: ts.isTypeOnlyImportOrExportDeclaration(node), + sourceName: node.propertyName?.text ?? node.name.text, + req: getReq(node.parent.parent.parent), + node, + }; + } + + // `export { bar } from './bar'` or `export { x }` + if (ts.isExportSpecifier(node)) { + // if there isn't a related module specifier then this export isn't a type of "import" + if (!node.parent.parent.moduleSpecifier) { + return; + } + + return { + type: 'named', + typesOnly: ts.isTypeOnlyImportOrExportDeclaration(node), + sourceName: node.propertyName?.text ?? node.name.text, + req: getReq(node.parent.parent), + node, + }; + } + + // import Foo from 'foo' + if (ts.isImportClause(node)) { + return { + type: 'default', + typesOnly: ts.isTypeOnlyImportOrExportDeclaration(node), + req: getReq(node.parent), + node, + }; + } + + // import * as Foo from 'foo' + if (ts.isNamespaceImport(node)) { + return { + type: 'namespace', + typesOnly: ts.isTypeOnlyImportOrExportDeclaration(node), + req: getReq(node.parent.parent), + node, + }; + } + + // export * as Foo from 'foo' + if (ts.isNamespaceExport(node)) { + return { + type: 'namespace', + typesOnly: ts.isTypeOnlyImportOrExportDeclaration(node), + req: getReq(node.parent), + node, + }; + } +} diff --git a/packages/kbn-type-summarizer/src/lib/log/cli_log.ts b/packages/kbn-type-summarizer/src/lib/log/cli_log.ts deleted file mode 100644 index 1121dfae3606a..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/log/cli_log.ts +++ /dev/null @@ -1,99 +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 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 { format } from 'util'; -import { dim, blueBright, yellowBright, redBright, gray } from 'chalk'; -import getopts from 'getopts'; - -import { Logger } from './logger'; - -const LOG_LEVEL_RANKS = { - silent: 0, - quiet: 1, - info: 2, - debug: 3, - verbose: 4, -}; -export type LogLevel = keyof typeof LOG_LEVEL_RANKS; -const LOG_LEVELS = (Object.keys(LOG_LEVEL_RANKS) as LogLevel[]).sort( - (a, b) => LOG_LEVEL_RANKS[a] - LOG_LEVEL_RANKS[b] -); -const LOG_LEVELS_DESC = LOG_LEVELS.slice().reverse(); - -type LogLevelMap = { [k in LogLevel]: boolean }; - -export interface LogWriter { - write(chunk: string): void; -} - -export class CliLog implements Logger { - static parseLogLevel(level: LogLevel) { - if (!LOG_LEVELS.includes(level)) { - throw new Error('invalid log level'); - } - - const rank = LOG_LEVEL_RANKS[level]; - return Object.fromEntries( - LOG_LEVELS.map((l) => [l, LOG_LEVEL_RANKS[l] <= rank]) - ) as LogLevelMap; - } - - static pickLogLevelFromFlags( - flags: getopts.ParsedOptions, - defaultLogLevl: LogLevel = 'info' - ): LogLevel { - for (const level of LOG_LEVELS_DESC) { - if (Object.prototype.hasOwnProperty.call(flags, level) && flags[level] === true) { - return level; - } - } - - return defaultLogLevl; - } - - private readonly map: LogLevelMap; - constructor(public readonly level: LogLevel, private readonly writeTo: LogWriter) { - this.map = CliLog.parseLogLevel(level); - } - - info(msg: string, ...args: any[]) { - if (this.map.info) { - this.writeTo.write(`${blueBright('info')} ${format(msg, ...args)}\n`); - } - } - - warn(msg: string, ...args: any[]) { - if (this.map.quiet) { - this.writeTo.write(`${yellowBright('warning')} ${format(msg, ...args)}\n`); - } - } - - error(msg: string, ...args: any[]) { - if (this.map.quiet) { - this.writeTo.write(`${redBright('error')} ${format(msg, ...args)}\n`); - } - } - - debug(msg: string, ...args: any[]) { - if (this.map.debug) { - this.writeTo.write(`${gray('debug')} ${format(msg, ...args)}\n`); - } - } - - verbose(msg: string, ...args: any[]) { - if (this.map.verbose) { - this.writeTo.write(`${dim('verbose')}: ${format(msg, ...args)}\n`); - } - } - - success(msg: string, ...args: any[]): void { - if (this.map.quiet) { - this.writeTo.write(`✅ ${format(msg, ...args)}\n`); - } - } -} diff --git a/packages/kbn-type-summarizer/src/lib/path.ts b/packages/kbn-type-summarizer/src/lib/path.ts deleted file mode 100644 index 79d56aa58fab6..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/path.ts +++ /dev/null @@ -1,36 +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 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 Path from 'path'; - -import normalizePath from 'normalize-path'; -const cwd = normalizePath(process.cwd()); - -export function cwdRelative(path: string) { - return relative(cwd, path); -} - -export function relative(from: string, to: string) { - return normalizePath(Path.relative(from, to)); -} - -export function join(...segments: string[]) { - return Path.join(...segments); -} - -export function dirname(path: string) { - return Path.dirname(path); -} - -export function resolve(path: string) { - return Path.isAbsolute(path) ? normalizePath(path) : join(cwd, path); -} - -export function isAbsolute(path: string) { - return Path.isAbsolute(path); -} diff --git a/packages/kbn-type-summarizer/src/lib/printer.ts b/packages/kbn-type-summarizer/src/lib/printer.ts deleted file mode 100644 index 8688ab2af66ec..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/printer.ts +++ /dev/null @@ -1,421 +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 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 * as ts from 'typescript'; -import { SourceNode, CodeWithSourceMap } from 'source-map'; - -import * as Path from './path'; -import { findKind } from './ts_nodes'; -import { SourceMapper } from './source_mapper'; -import { ExportInfo } from './export_info'; -import { CollectorResult, ImportedSymbol } from './export_collector'; - -type SourceNodes = Array; -const COMMENT_TRIM = /^(\s+)(\/\*|\*|\/\/)/; - -export class Printer { - private readonly tsPrint = ts.createPrinter({ - newLine: ts.NewLineKind.LineFeed, - noEmitHelpers: true, - omitTrailingSemicolon: false, - removeComments: true, - }); - - constructor( - private readonly sourceMapper: SourceMapper, - private readonly results: CollectorResult[], - private readonly outputPath: string, - private readonly mapOutputPath: string, - private readonly sourceRoot: string, - private readonly strict: boolean - ) {} - - async print(): Promise { - const file = new SourceNode( - null, - null, - null, - this.results.flatMap((r) => { - if (r.type === 'reference') { - return `/// \n`; - } - - if (r.type === 'imports') { - return this.printImports(r.imports); - } - - return this.toSourceNodes(r.node, r.exportInfo); - }) - ); - - const outputDir = Path.dirname(this.outputPath); - const mapOutputDir = Path.dirname(this.mapOutputPath); - - const output = file.toStringWithSourceMap({ - file: Path.relative(mapOutputDir, this.outputPath), - sourceRoot: this.sourceRoot, - }); - - const nl = output.code.endsWith('\n') ? '' : '\n'; - const sourceMapPathRel = Path.relative(outputDir, this.mapOutputPath); - output.code += `${nl}//# sourceMappingURL=${sourceMapPathRel}`; - - return output; - } - - private printImports(imports: ImportedSymbol[]) { - const importLines: string[] = []; - const exportLines: string[] = []; - - for (const imp of imports) { - const from = ` from '${imp.moduleId}';`; - - if (imp.remoteName === 'default') { - importLines.push( - [imp.isTypeOnly ? `import type ` : 'import ', imp.localName, from].join('') - ); - } else if (imp.remoteName === '*') { - importLines.push( - [imp.isTypeOnly ? `import type * as ` : 'import * as ', imp.localName, from].join('') - ); - } else { - importLines.push( - [ - imp.isTypeOnly ? 'import type { ' : 'import { ', - imp.localName ? `${imp.remoteName} as ${imp.localName}` : imp.remoteName, - ' }', - from, - ].join('') - ); - } - - if (imp.exportInfo) { - exportLines.push(`${imp.exportInfo.type} { ${imp.localName || imp.remoteName} };`); - } - } - - const lines = [ - ...importLines, - ...(importLines.length && exportLines.length ? [''] : []), - ...exportLines, - ]; - return lines.length ? lines.join('\n') + '\n\n' : ''; - } - - private getDeclarationKeyword(node: ts.Declaration) { - if (node.kind === ts.SyntaxKind.FunctionDeclaration) { - return 'function'; - } - - if (node.kind === ts.SyntaxKind.TypeAliasDeclaration) { - return 'type'; - } - - if (node.kind === ts.SyntaxKind.ClassDeclaration) { - return 'class'; - } - - if (node.kind === ts.SyntaxKind.InterfaceDeclaration) { - return 'interface'; - } - - if (node.kind === ts.SyntaxKind.EnumDeclaration) { - return 'enum'; - } - - if (ts.isVariableDeclaration(node)) { - return this.getVariableDeclarationType(node); - } - } - - private printModifiers(exportInfo: ExportInfo | undefined, node: ts.Declaration) { - const flags = ts.getCombinedModifierFlags(node); - const keyword = this.getDeclarationKeyword(node); - const modifiers: string[] = []; - if (exportInfo) { - // always use `export` for explicit types - if (keyword) { - modifiers.push('export'); - } else { - modifiers.push(exportInfo.type); - } - } - if ((keyword === 'var' || keyword === 'const') && !exportInfo) { - modifiers.push('declare'); - } - if (flags & ts.ModifierFlags.Default) { - modifiers.push('default'); - } - if (flags & ts.ModifierFlags.Abstract) { - modifiers.push('abstract'); - } - if (flags & ts.ModifierFlags.Private) { - modifiers.push('private'); - } - if (flags & ts.ModifierFlags.Public) { - modifiers.push('public'); - } - if (flags & ts.ModifierFlags.Static) { - modifiers.push('static'); - } - if (flags & ts.ModifierFlags.Readonly) { - modifiers.push('readonly'); - } - if (flags & ts.ModifierFlags.Const) { - modifiers.push('const'); - } - if (flags & ts.ModifierFlags.Async) { - modifiers.push('async'); - } - - if (keyword) { - modifiers.push(keyword); - } - - return `${modifiers.join(' ')} `; - } - - private printNode(node: ts.Node) { - return this.tsPrint.printNode( - ts.EmitHint.Unspecified, - node, - this.sourceMapper.getSourceFile(node) - ); - } - - private ensureNewline(string: string): string; - private ensureNewline(string: SourceNodes): SourceNodes; - private ensureNewline(string: string | SourceNodes): string | SourceNodes { - if (typeof string === 'string') { - return string.endsWith('\n') ? string : `${string}\n`; - } - - const end = string.at(-1); - if (end === undefined) { - return []; - } - - const valid = (typeof end === 'string' ? end : end.toString()).endsWith('\n'); - return valid ? string : [...string, '\n']; - } - - private getMappedSourceNode(node: ts.Node, code?: string) { - return this.sourceMapper.getSourceNode(node, code ?? node.getText()); - } - - private getVariableDeclarationList(node: ts.VariableDeclaration) { - const list = node.parent; - if (!ts.isVariableDeclarationList(list)) { - const kind = findKind(list); - throw new Error( - `expected parent of variable declaration to be a VariableDeclarationList, got [${kind}]` - ); - } - return list; - } - - private getVariableDeclarationType(node: ts.VariableDeclaration) { - const flags = ts.getCombinedNodeFlags(this.getVariableDeclarationList(node)); - if (flags & ts.NodeFlags.Const) { - return 'const'; - } - if (flags & ts.NodeFlags.Let) { - return 'let'; - } - return 'var'; - } - - private getSourceWithLeadingComments(node: ts.Node) { - // variable declarations regularly have leading comments but they're two-parents up, so we have to handle them separately - if (!ts.isVariableDeclaration(node)) { - return node.getFullText(); - } - - const list = this.getVariableDeclarationList(node); - if (list.declarations.length > 1) { - return node.getFullText(); - } - - const statement = list.parent; - if (!ts.isVariableStatement(statement)) { - throw new Error('expected parent of VariableDeclarationList to be a VariableStatement'); - } - - return statement.getFullText(); - } - - private getLeadingComments(node: ts.Node, indentWidth = 0): string[] { - const fullText = this.getSourceWithLeadingComments(node); - const ranges = ts.getLeadingCommentRanges(fullText, 0); - if (!ranges) { - return []; - } - const indent = ' '.repeat(indentWidth); - - return ranges.flatMap((range) => { - const comment = fullText - .slice(range.pos, range.end) - .split('\n') - .map((line) => { - const match = line.match(COMMENT_TRIM); - if (!match) { - return line; - } - - const [, spaces, type] = match; - return line.slice(type === '*' ? spaces.length - 1 : spaces.length); - }) - .map((line) => `${indent}${line}`) - .join('\n'); - - if (comment.startsWith('/// this.printNode(p)).join(', ')}>`; - } - - private toSourceNodes(node: ts.Node, exportInfo?: ExportInfo): SourceNodes { - switch (node.kind) { - case ts.SyntaxKind.LiteralType: - case ts.SyntaxKind.StringLiteral: - case ts.SyntaxKind.BigIntLiteral: - case ts.SyntaxKind.NumericLiteral: - case ts.SyntaxKind.StringKeyword: - case ts.SyntaxKind.TypeReference: - case ts.SyntaxKind.IntersectionType: - return [node.getFullText().trim()]; - } - - if (ts.isEnumDeclaration(node)) { - return [node.getFullText().trim() + '\n']; - } - - if (ts.isFunctionDeclaration(node)) { - // we are just trying to replace the name with a sourceMapped node, so if there - // is no name just return the source - if (!node.name) { - return [node.getFullText()]; - } - - return [ - this.getLeadingComments(node), - this.printModifiers(exportInfo, node), - this.getMappedSourceNode(node.name, exportInfo?.name), - this.printTypeParameters(node), - `(${node.parameters.map((p) => p.getFullText()).join(', ')})`, - node.type ? [': ', this.printNode(node.type), ';'] : ';', - '\n', - ].flat(); - } - - if (ts.isInterfaceDeclaration(node)) { - const text = node.getText(); - const name = node.name.getText(); - const nameI = text.indexOf(name); - if (nameI === -1) { - throw new Error(`printed version of interface does not include name [${name}]: ${text}`); - } - return [ - ...this.getLeadingComments(node), - text.slice(0, nameI), - this.getMappedSourceNode(node.name, name), - text.slice(nameI + name.length), - '\n', - ]; - } - - if (ts.isVariableDeclaration(node)) { - return [ - ...this.getLeadingComments(node), - this.printModifiers(exportInfo, node), - this.getMappedSourceNode(node.name), - ...(node.type ? [': ', this.printNode(node.type)] : []), - ...(node.initializer ? [' = ', this.printNode(node.initializer)] : []), - ';\n', - ]; - } - - if (ts.isUnionTypeNode(node)) { - return node.types.flatMap((type, i) => - i > 0 ? [' | ', ...this.toSourceNodes(type)] : this.toSourceNodes(type) - ); - } - - if (ts.isTypeAliasDeclaration(node)) { - return [ - ...this.getLeadingComments(node), - this.printModifiers(exportInfo, node), - this.getMappedSourceNode(node.name), - this.printTypeParameters(node), - ' = ', - this.ensureNewline(this.toSourceNodes(node.type)), - ].flat(); - } - - if (ts.isClassDeclaration(node)) { - return [ - ...this.getLeadingComments(node), - this.printModifiers(exportInfo, node), - node.name ? this.getMappedSourceNode(node.name) : [], - this.printTypeParameters(node), - node.heritageClauses - ? ` ${node.heritageClauses.map((c) => c.getFullText().trim()).join(' ')}` - : [], - ' {\n', - node.members.flatMap((m) => { - const memberText = m.getText(); - - if (ts.isConstructorDeclaration(m)) { - return ` ${memberText}\n`; - } - - if (!m.name) { - return ` ${memberText}\n`; - } - - const nameText = m.name.getText(); - const pos = memberText.indexOf(nameText); - if (pos === -1) { - return ` ${memberText}\n`; - } - - const left = memberText.slice(0, pos); - const right = memberText.slice(pos + nameText.length); - const nameNode = this.getMappedSourceNode(m.name, nameText); - - return [...this.getLeadingComments(m, 2), ` `, left, nameNode, right, `\n`]; - }), - '}\n', - ].flat(); - } - - if (!this.strict) { - return [this.ensureNewline(this.printNode(node))]; - } else { - throw new Error(`unable to print export type of kind [${findKind(node)}]`); - } - } -} diff --git a/packages/kbn-type-summarizer/src/lib/source_file_mapper.ts b/packages/kbn-type-summarizer/src/lib/source_file_mapper.ts new file mode 100644 index 0000000000000..0622d31fed5b0 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/source_file_mapper.ts @@ -0,0 +1,36 @@ +/* + * 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 * as ts from 'typescript'; +import { Path } from '@kbn/type-summarizer-core'; +import isPathInside from 'is-path-inside'; + +/** + * Wrapper class around helpers for determining information + * about source files. + */ +export class SourceFileMapper { + constructor(private readonly dtsDir: string) {} + + getAbsolute(node: ts.Node) { + return node.getSourceFile().fileName; + } + + isNodeModule(path: string) { + return ( + isPathInside(path, this.dtsDir) ? Path.relative(this.dtsDir, path) : Path.toNormal(path) + ) + .split('/') + .includes('node_modules'); + } + + isExternal(node: ts.Node) { + const path = this.getAbsolute(node); + return this.isNodeModule(path); + } +} diff --git a/packages/kbn-type-summarizer/src/lib/source_mapper.ts b/packages/kbn-type-summarizer/src/lib/source_mapper.ts index 1f03119e8e3fd..86f336a44f19a 100644 --- a/packages/kbn-type-summarizer/src/lib/source_mapper.ts +++ b/packages/kbn-type-summarizer/src/lib/source_mapper.ts @@ -9,46 +9,53 @@ import * as ts from 'typescript'; import { SourceNode, SourceMapConsumer, BasicSourceMapConsumer } from 'source-map'; -import { Logger } from './log'; -import { tryReadFile } from './helpers/fs'; -import { parseJson } from './helpers/json'; -import { isNodeModule } from './is_node_module'; -import * as Path from './path'; +import { Logger, tryReadFile, parseJson, Path, describeNode } from '@kbn/type-summarizer-core'; + +import { SourceFileMapper } from './source_file_mapper'; type SourceMapConsumerEntry = [ts.SourceFile, BasicSourceMapConsumer | undefined]; +/** + * Wrapper class for utilities that deal with reading the source maps produced by + * tsc along with the .d.ts, as well as creating the SourceNode instances we use to + * create our type summary along with source maps. + */ export class SourceMapper { static async forSourceFiles( log: Logger, - dtsDir: string, + sources: SourceFileMapper, repoRelativePackageDir: string, - sourceFiles: readonly ts.SourceFile[] + program: ts.Program ) { const entries = await Promise.all( - sourceFiles.map(async (sourceFile): Promise => { - if (isNodeModule(dtsDir, sourceFile.fileName)) { - return; - } - - const text = sourceFile.getText(); - const match = text.match(/^\/\/#\s*sourceMappingURL=(.*)/im); - if (!match) { - return [sourceFile, undefined]; - } - - const relSourceFile = Path.cwdRelative(sourceFile.fileName); - const sourceMapPath = Path.join(Path.dirname(sourceFile.fileName), match[1]); - const relSourceMapPath = Path.cwdRelative(sourceMapPath); - const sourceJson = await tryReadFile(sourceMapPath, 'utf8'); - if (!sourceJson) { - throw new Error( - `unable to find source map for [${relSourceFile}] expected at [${match[1]}]` - ); - } - - const json = parseJson(sourceJson, `source map at [${relSourceMapPath}]`); - return [sourceFile, await new SourceMapConsumer(json)]; - }) + program + .getSourceFiles() + .filter((f) => !sources.isNodeModule(f.fileName)) + .sort((a, b) => a.fileName.localeCompare(b.fileName)) + .map(async (sourceFile): Promise => { + if (sources.isNodeModule(sourceFile.fileName)) { + return; + } + + const text = sourceFile.getText(); + const match = text.match(/^\/\/#\s*sourceMappingURL=(.*)/im); + if (!match) { + return [sourceFile, undefined]; + } + + const relSourceFile = Path.cwdRelative(sourceFile.fileName); + const sourceMapPath = Path.join(Path.dirname(sourceFile.fileName), match[1]); + const relSourceMapPath = Path.cwdRelative(sourceMapPath); + const sourceJson = await tryReadFile(sourceMapPath, 'utf8'); + if (!sourceJson) { + throw new Error( + `unable to find source map for [${relSourceFile}] expected at [${match[1]}]` + ); + } + + const json = parseJson(sourceJson, `source map at [${relSourceMapPath}]`); + return [sourceFile, await new SourceMapConsumer(json)]; + }) ); const consumers = new Map(entries.filter((e): e is SourceMapConsumerEntry => !!e)); @@ -78,39 +85,12 @@ export class SourceMapper { * the absolute version of the `repoRelativePackageDir` to the absolute version of the `source`, which should give * us the path to the source, relative to the `repoRelativePackageDir`. */ - fixSourcePath(source: string) { + private fixSourcePath(source: string) { return Path.relative(this.sourceFixDir, Path.join('/', source)); } - getSourceNode(generatedNode: ts.Node, code: string) { - const pos = this.findOriginalPosition(generatedNode); - - if (pos) { - return new SourceNode(pos.line, pos.column, pos.source, code, pos.name ?? undefined); - } - - return new SourceNode(null, null, null, code); - } - - sourceFileCache = new WeakMap(); - // abstracted so we can cache this - getSourceFile(node: ts.Node): ts.SourceFile { - if (ts.isSourceFile(node)) { - return node; - } - - const cached = this.sourceFileCache.get(node); - if (cached) { - return cached; - } - - const sourceFile = this.getSourceFile(node.parent); - this.sourceFileCache.set(node, sourceFile); - return sourceFile; - } - - findOriginalPosition(node: ts.Node) { - const dtsSource = this.getSourceFile(node); + private findOriginalPosition(node: ts.Node) { + const dtsSource = node.getSourceFile(); if (!this.consumers.has(dtsSource)) { throw new Error(`sourceFile for [${dtsSource.fileName}] didn't have sourcemaps loaded`); @@ -135,9 +115,35 @@ export class SourceMapper { }; } + getOriginalSourcePath(sourceFile: ts.SourceFile) { + const consumer = this.consumers.get(sourceFile); + if (!consumer) { + throw new Error(`no source map defined for ${describeNode(sourceFile)}`); + } + + if (consumer.sources.length !== 1) { + throw new Error( + `tsc sourcemap produced ${ + consumer.sources.length + } source entries, expected 1: ${describeNode(sourceFile)}` + ); + } + + return this.fixSourcePath(consumer.sources[0]); + } + + getSourceNode(generatedNode: ts.Node, code: string) { + const pos = this.findOriginalPosition(generatedNode); + + if (pos && pos.line && pos.column && pos.source) { + return new SourceNode(pos.line, pos.column, pos.source, code, pos.name ?? undefined); + } + } + close() { for (const consumer of this.consumers.values()) { consumer?.destroy(); } + this.consumers.clear(); } } diff --git a/packages/kbn-type-summarizer/src/lib/symbol_resolver.ts b/packages/kbn-type-summarizer/src/lib/symbol_resolver.ts new file mode 100644 index 0000000000000..299cc8d38943c --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/symbol_resolver.ts @@ -0,0 +1,58 @@ +/* + * 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 ts from 'typescript'; + +import { Logger, isAliasSymbol, CliError, describeNode } from '@kbn/type-summarizer-core'; +import { DecSymbol, isDecSymbol } from './ts_nodes'; +import { getImportDetails } from './import_details'; + +/** + * Wrapper class around utilities for resolving symbols and producing meaningful errors when those + * symbols can't be resolved properly. + */ +export class SymbolResolver { + constructor(private readonly typeChecker: ts.TypeChecker, private readonly log: Logger) {} + + getForIdentifier(id: ts.Identifier) { + return this.log.verboseStep('symbols.getForIdentifier()', id, () => { + const symbol = this.typeChecker.getSymbolAtLocation(id); + if (!symbol) { + throw new Error(`unable to find symbol for ${describeNode(id)}`); + } + + return symbol; + }); + } + + toRootSymbol(alias: ts.Symbol, source?: ts.Node): DecSymbol { + return this.log.verboseStep('symbols.toRootSymbol()', alias, () => { + const root = isAliasSymbol(alias) ? this.typeChecker.getAliasedSymbol(alias) : alias; + + if (!isDecSymbol(root)) { + const importDetails = [...(alias.declarations ?? []), ...(source ? [source] : [])].flatMap( + (d) => getImportDetails(d) ?? [] + ); + + if (importDetails.length) { + throw new CliError( + `unable to find declarations for symbol imported from "${ + importDetails[0].req + }". If this is an external module, make sure is it listed in the type dependencies for this package. If it's internal then make sure that TypeScript understands the types of the imported value. Imported: ${describeNode( + importDetails[0].node + )}` + ); + } + + throw new Error('expected symbol to have declarations'); + } + + return root; + }); + } +} diff --git a/packages/kbn-type-summarizer/src/lib/ts_nodes.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes.ts deleted file mode 100644 index f3e2209651ba9..0000000000000 --- a/packages/kbn-type-summarizer/src/lib/ts_nodes.ts +++ /dev/null @@ -1,75 +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 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 * as ts from 'typescript'; - -export type ValueNode = - | ts.ClassDeclaration - | ts.FunctionDeclaration - | ts.TypeAliasDeclaration - | ts.VariableDeclaration - | ts.InterfaceDeclaration - | ts.EnumDeclaration; - -export function isExportedValueNode(node: ts.Node): node is ValueNode { - return ( - node.kind === ts.SyntaxKind.ClassDeclaration || - node.kind === ts.SyntaxKind.FunctionDeclaration || - node.kind === ts.SyntaxKind.TypeAliasDeclaration || - node.kind === ts.SyntaxKind.VariableDeclaration || - node.kind === ts.SyntaxKind.InterfaceDeclaration || - node.kind === ts.SyntaxKind.EnumDeclaration - ); -} -export function assertExportedValueNode(node: ts.Node): asserts node is ValueNode { - if (!isExportedValueNode(node)) { - const kind = findKind(node); - throw new Error(`not a valid ExportedValueNode [kind=${kind}]`); - } -} -export function toExportedNodeValue(node: ts.Node): ValueNode { - assertExportedValueNode(node); - return node; -} - -export function findKind(node: ts.Node) { - for (const [name, value] of Object.entries(ts.SyntaxKind)) { - if (node.kind === value) { - return name; - } - } - - throw new Error('node.kind is not in the SyntaxKind map'); -} - -export type DecSymbol = ts.Symbol & { - declarations: NonNullable; -}; -export function isDecSymbol(symbol: ts.Symbol): symbol is DecSymbol { - return !!symbol.declarations; -} -export function assertDecSymbol(symbol: ts.Symbol): asserts symbol is DecSymbol { - if (!isDecSymbol(symbol)) { - throw new Error('symbol has no declarations'); - } -} -export function toDecSymbol(symbol: ts.Symbol): DecSymbol { - assertDecSymbol(symbol); - return symbol; -} - -export type ExportFromDeclaration = ts.ExportDeclaration & { - moduleSpecifier: NonNullable; -}; -export function isExportFromDeclaration(node: ts.Node): node is ExportFromDeclaration { - return ts.isExportDeclaration(node) && !!node.moduleSpecifier; -} - -export function isAliasSymbol(symbol: ts.Symbol) { - return symbol.flags & ts.SymbolFlags.Alias; -} diff --git a/packages/kbn-type-summarizer/src/lib/ts_nodes/dec_symbol.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes/dec_symbol.ts new file mode 100644 index 0000000000000..c4443ed25de9c --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ts_nodes/dec_symbol.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 ts from 'typescript'; + +export type DecSymbol = ts.Symbol & { + declarations: NonNullable; +}; + +export function isDecSymbol(symbol: ts.Symbol): symbol is DecSymbol { + return !!symbol.declarations && symbol.declarations.length > 0; +} + +export function assertDecSymbol(symbol: ts.Symbol): asserts symbol is DecSymbol { + if (!isDecSymbol(symbol)) { + throw new Error(`Expected symbol to have declarations.`); + } +} + +export function toDecSymbol(symbol: ts.Symbol): DecSymbol { + assertDecSymbol(symbol); + return symbol; +} + +export function getSymbolDeclarations(symbol: ts.Symbol) { + assertDecSymbol(symbol); + return symbol.declarations; +} diff --git a/packages/kbn-type-summarizer/src/lib/ts_nodes/export_from.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes/export_from.ts new file mode 100644 index 0000000000000..bbedffd232753 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ts_nodes/export_from.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 ts from 'typescript'; + +export type ExportFromDeclaration = ts.ExportDeclaration & { + moduleSpecifier: NonNullable; +}; + +export function isExportFromDeclaration(node: ts.Node): node is ExportFromDeclaration { + return ts.isExportDeclaration(node) && !!node.moduleSpecifier; +} diff --git a/packages/kbn-type-summarizer/src/lib/ts_nodes/exportable_node.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes/exportable_node.ts new file mode 100644 index 0000000000000..c9657523104bf --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ts_nodes/exportable_node.ts @@ -0,0 +1,54 @@ +/* + * 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 ts from 'typescript'; + +import { describeNode, hasIdentifierName } from '@kbn/type-summarizer-core'; + +export type ExportableDec = ( + | ts.ClassDeclaration + | ts.FunctionDeclaration + | ts.TypeAliasDeclaration + | ts.VariableDeclaration + | ts.InterfaceDeclaration + | ts.EnumDeclaration + | ts.ModuleDeclaration +) & { name: ts.Identifier }; + +export function isExportableDec(node: ts.Node): node is ExportableDec { + return ( + (node.kind === ts.SyntaxKind.ClassDeclaration || + node.kind === ts.SyntaxKind.FunctionDeclaration || + node.kind === ts.SyntaxKind.TypeAliasDeclaration || + node.kind === ts.SyntaxKind.VariableDeclaration || + node.kind === ts.SyntaxKind.InterfaceDeclaration || + node.kind === ts.SyntaxKind.EnumDeclaration || + node.kind === ts.SyntaxKind.ModuleDeclaration) && + hasIdentifierName(node) + ); +} + +export function assertExportableDec(node: ts.Node): asserts node is ExportableDec { + if (!isExportableDec(node)) { + throw new Error(`not a valid ExportableDec ${describeNode(node)}`); + } +} + +export function toExportableDec(node: ts.Node): ExportableDec { + assertExportableDec(node); + return node; +} + +export function isTypeDeclaration(dec: ExportableDec) { + return ( + ts.isInterfaceDeclaration(dec) || + ts.isTypeAliasDeclaration(dec) || + ts.isEnumDeclaration(dec) || + ts.isModuleDeclaration(dec) + ); +} diff --git a/packages/kbn-type-summarizer/src/lib/ts_nodes/imports.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes/imports.ts new file mode 100644 index 0000000000000..fefde651a0aef --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ts_nodes/imports.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 ts from 'typescript'; + +import { describeNode } from '@kbn/type-summarizer-core'; + +export interface ImportDescriptor { + declaration: ts.ImportDeclaration | ts.ExportDeclaration; + moduleSpecifier: string; +} + +export function getImportDescriptor( + specifier: ts.ImportSpecifier | ts.ExportSpecifier +): ImportDescriptor | undefined { + const declaration = ts.isImportSpecifier(specifier) + ? // import specifiers are always within NamedImports nodes + // which are always with ImportClause nodes + // which are always within ImportDeclaration nodes + specifier.parent.parent.parent + : // export specifiers are always within NamedExports nodes + // which are always within ExportDeclaration nodes + specifier.parent.parent; + + if (declaration.moduleSpecifier && ts.isStringLiteral(declaration.moduleSpecifier)) { + return { + declaration, + moduleSpecifier: declaration.moduleSpecifier.text, + }; + } + + if (ts.isImportDeclaration(declaration) && !ts.isStringLiteral(declaration.moduleSpecifier)) { + throw new Error( + `SyntaxError: ImportDeclaration.moduleSpecifier must be a string literal ${describeNode( + declaration + )}` + ); + } + + return undefined; +} diff --git a/packages/kbn-type-summarizer/src/lib/ts_nodes/index.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes/index.ts new file mode 100644 index 0000000000000..148c8d5a870ed --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/ts_nodes/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. + */ + +export * from './dec_symbol'; +export * from './export_from'; +export * from './exportable_node'; +export * from './imports'; +export * from './syntax_kind'; diff --git a/packages/kbn-type-summarizer/src/lib/is_node_module.ts b/packages/kbn-type-summarizer/src/lib/ts_nodes/syntax_kind.ts similarity index 55% rename from packages/kbn-type-summarizer/src/lib/is_node_module.ts rename to packages/kbn-type-summarizer/src/lib/ts_nodes/syntax_kind.ts index ba4d607ccb864..7d4a4a80761cf 100644 --- a/packages/kbn-type-summarizer/src/lib/is_node_module.ts +++ b/packages/kbn-type-summarizer/src/lib/ts_nodes/syntax_kind.ts @@ -6,12 +6,15 @@ * Side Public License, v 1. */ -import isPathInside from 'is-path-inside'; +import ts from 'typescript'; -import * as Path from './path'; - -export function isNodeModule(dtsDir: string, path: string) { - return (isPathInside(path, dtsDir) ? Path.relative(dtsDir, path) : path) - .split('/') - .includes('node_modules'); +export function assertKind( + node: ts.Node, + test: (n: ts.Node) => n is T +): asserts node is T { + if (!test(node)) { + throw new Error( + `expected node to match [${test.name}], actual kind: ${ts.SyntaxKind[node.kind]}` + ); + } } diff --git a/packages/kbn-type-summarizer/src/lib/ts_project.ts b/packages/kbn-type-summarizer/src/lib/ts_project.ts index 92946e3290449..9d3a43ba11130 100644 --- a/packages/kbn-type-summarizer/src/lib/ts_project.ts +++ b/packages/kbn-type-summarizer/src/lib/ts_project.ts @@ -8,9 +8,12 @@ import * as ts from 'typescript'; -export function createTsProject(tsConfig: ts.ParsedCommandLine, inputPaths: string[]) { +/** + * Parse a tsconfig file and list of input files into a TypeScript Program object. + */ +export function createTsProject(tsConfig: ts.ParsedCommandLine, rootNames: string[]) { return ts.createProgram({ - rootNames: inputPaths, + rootNames, options: { ...tsConfig.options, skipLibCheck: false, diff --git a/packages/kbn-type-summarizer/src/lib/tsconfig_file.ts b/packages/kbn-type-summarizer/src/lib/tsconfig_file.ts index f3d491c93abcb..d8ecd1b559a5e 100644 --- a/packages/kbn-type-summarizer/src/lib/tsconfig_file.ts +++ b/packages/kbn-type-summarizer/src/lib/tsconfig_file.ts @@ -8,10 +8,12 @@ import * as ts from 'typescript'; -import * as Path from './path'; -import { CliError } from './cli_error'; +import { CliError, Path } from '@kbn/type-summarizer-core'; -export function readTsConfigFile(path: string) { +/** + * Read TS's special variable of JSON from a file into a plain object + */ +function readTsConfigFile(path: string) { const json = ts.readConfigFile(path, ts.sys.readFile); if (json.error) { @@ -21,6 +23,9 @@ export function readTsConfigFile(path: string) { return json.config; } +/** + * Read a tsconfig.json file from dist and parse it using utilities from the typscript package. + */ export function loadTsConfigFile(path: string) { return ts.parseJsonConfigFileContent(readTsConfigFile(path) ?? {}, ts.sys, Path.dirname(path)); } diff --git a/packages/kbn-type-summarizer/src/lib/type_summary/export_some_name.ts b/packages/kbn-type-summarizer/src/lib/type_summary/export_some_name.ts new file mode 100644 index 0000000000000..d8616d5ae4923 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/type_summary/export_some_name.ts @@ -0,0 +1,18 @@ +/* + * 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 { NamedExportDetails } from '../export_details'; + +/** + * Create an export statement for some name already in scope + */ +export const exportSomeName = ({ name, typeOnly }: NamedExportDetails, localName: string) => { + return `export ${typeOnly ? `type ` : ''}{${ + name === localName ? name : `${localName} as ${name}` + }}\n`; +}; diff --git a/packages/kbn-type-summarizer/src/lib/log/index.ts b/packages/kbn-type-summarizer/src/lib/type_summary/index.ts similarity index 81% rename from packages/kbn-type-summarizer/src/lib/log/index.ts rename to packages/kbn-type-summarizer/src/lib/type_summary/index.ts index 68a37528d4976..b12e1d0c22090 100644 --- a/packages/kbn-type-summarizer/src/lib/log/index.ts +++ b/packages/kbn-type-summarizer/src/lib/type_summary/index.ts @@ -6,6 +6,4 @@ * Side Public License, v 1. */ -export * from './logger'; -export * from './cli_log'; -export * from './test_log'; +export { printTypeSummary } from './print_type_summary'; diff --git a/packages/kbn-type-summarizer/src/lib/type_summary/print_imports.ts b/packages/kbn-type-summarizer/src/lib/type_summary/print_imports.ts new file mode 100644 index 0000000000000..542b4bc3c3e2a --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/type_summary/print_imports.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 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 { Logger } from '@kbn/type-summarizer-core'; +import { SourceNode } from 'source-map'; + +import { ImportedDecs } from '../ast_index'; + +import { TypeSummaryNamer } from './type_summary_namer'; +import { exportSomeName } from './export_some_name'; + +/** + * Convert an import request into a usable keyword, for when we don't have much information about a good name for an import + */ +const reqToKeyword = (req: string) => + req.split(/[A-Z\W]/).reduce((acc, chunk) => { + if (!chunk) { + return acc; + } + if (acc) { + return acc + chunk[0].toUpperCase() + chunk.slice(1).toLowerCase(); + } + return chunk.toLowerCase(); + }, ''); + +/** + * Reads the imports from the `index` and adds the necessary `SourceNode`s to the `source` for each. + */ +export function printImports( + imports: ImportedDecs[], + names: TypeSummaryNamer, + log: Logger, + source: SourceNode +) { + log.step('printImports()', `${imports.length} imports`, () => { + for (const i of imports) { + const name = names.get( + i.rootSymbol, + // if we don't use it locally, don't try to re-use its name + i.localUsageCount ? i.details.node.name?.getText() ?? reqToKeyword(i.details.req) : '_' + ); + + if (i.details.type === 'default') { + source.add(`import ${name} from '${i.details.req}'\n`); + for (const exported of i.exports) { + if (exported.type === 'default') { + source.add(`export default ${name}\n`); + } else { + source.add(exportSomeName(exported, name)); + } + } + } else if (i.details.type === 'namespace') { + source.add(`import * as ${name} from '${i.details.req}'\n`); + for (const exported of i.exports) { + if (exported.type === 'default') { + source.add(`export default ${name}\n`); + } else { + source.add(exportSomeName(exported, name)); + } + } + } else { + const { details, exports, localUsageCount } = i; + + let imported = false; + const ensureImported = () => { + if (!imported) { + imported = true; + source.add( + `import { ${ + details.sourceName !== name ? `${details.sourceName} as ${name}` : name + } } from '${details.req}'\n` + ); + } + }; + + if (localUsageCount) { + ensureImported(); + } + + for (const exported of exports) { + if (exported.type === 'default') { + ensureImported(); + source.add(`export default ${name}\n`); + } else { + source.add( + `export ${exported.typeOnly ? `type ` : ''}{ ${ + exported.name !== details.sourceName + ? `${details.sourceName} as ${exported.name}` + : details.sourceName + } } from '${details.req}'\n` + ); + } + } + } + } + + source.add('\n'); + }); +} diff --git a/packages/kbn-type-summarizer/src/lib/type_summary/print_locals.ts b/packages/kbn-type-summarizer/src/lib/type_summary/print_locals.ts new file mode 100644 index 0000000000000..332b34b6d731e --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/type_summary/print_locals.ts @@ -0,0 +1,142 @@ +/* + * 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 { Logger } from '@kbn/type-summarizer-core'; +import { SourceNode } from 'source-map'; + +import { isTypeDeclaration } from '../ts_nodes'; +import { LocalDecs } from '../ast_index'; +import { SourceMapper } from '../source_mapper'; +import { DtsSnipper } from '../dts_snipper'; + +import { TypeSummaryNamer } from './type_summary_namer'; +import { exportSomeName } from './export_some_name'; + +/** + * Reads `locals` and adds necessary `SourceNode`s to `source` to reproduce the declarations + * of each local. + * + * Local printing is primarily done using the `DtsSnipper` which reads the original definition + * of the given declaration from the .d.ts files produced by tsc, then breaks them up into + * "snippets" (more details in the DtsSnipper class). These snippets are then itterated to either + * produce SourceNodes or text for the resulting definition. + * + * The exception is NamespaceDec locals, which must synthesize an imported namespace either + * for local usage or for exporting. When a namespace import is used a structure similar to + * the following will be added to the type summary: + * + * declare namespace NamespaceName { + * export { + * foo, + * bar, + * baz, + * } + * } + * export { NamespaceName } + */ +export function printLocals( + locals: LocalDecs[], + names: TypeSummaryNamer, + sourceMaps: SourceMapper, + snipper: DtsSnipper, + log: Logger, + source: SourceNode +) { + const localDecCount = locals.reduce( + (acc, l) => acc + (l.type === 'namespace dec' ? 1 : l.decs.length), + 0 + ); + + log.step('printLocals()', `${localDecCount} decs`, () => { + for (const local of locals) { + if (local.type === 'namespace dec') { + const name = names.get( + local.rootSymbol, + local.exported?.type === 'named' ? local.exported.name : 'ns' + ); + + // synthesize the namespace that represents the namespace import + source.add([ + `declare namespace `, + new SourceNode(1, 0, sourceMaps.getOriginalSourcePath(local.sourceFile), name), + ` {\n`, + ]); + source.add(` export {\n`); + // members + for (const [memberName, symbol] of local.members) { + const refName = names.get(symbol, memberName); + source.add( + ` ${memberName === refName ? memberName : `${refName} as ${memberName}`},\n` + ); + } + source.add(` }\n`); + source.add(`}\n`); + + if (local.exported?.type === 'named') { + source.add(exportSomeName(local.exported, name)); + } + + if (local.exported?.type === 'default') { + source.add(`export default ${name}`); + } + + continue; + } + + const decName = names.get( + local.rootSymbol, + local.exported?.type === 'named' ? local.exported.name : local.decs[0].name.getText() + ); + const exportLocally = + local.exported?.type === 'named' && + (local.decs.every(isTypeDeclaration) || !local.exported.typeOnly) && + decName === local.exported.name; + + for (const dec of local.decs) { + for (const s of snipper.toSnippets(dec)) { + if (s.type === 'source') { + source.add(s.value); + continue; + } + + if (s.type === 'export') { + // only print the export if we are exporting locally, otherwise drop this snipped + if (exportLocally) { + if (local.exported?.type === 'default') { + source.add(`export default `); + } else { + source.add(`export `); + } + } else { + if (s.noExportRequiresDeclare) { + source.add(`declare `); + } + } + continue; + } + + const name = names.get(s.rootSymbol, s.text); + source.add(s.structural ? sourceMaps.getSourceNode(s.identifier, name) ?? name : name); + } + + source.add('\n'); + } + + if (!exportLocally) { + if (local.exported?.type === 'named') { + source.add(exportSomeName(local.exported, decName)); + } + if (local.exported?.type === 'default') { + source.add(`export default ${decName}\n`); + } + } + + source.add('\n'); + } + }); +} diff --git a/packages/kbn-type-summarizer/src/lib/type_summary/print_type_summary.ts b/packages/kbn-type-summarizer/src/lib/type_summary/print_type_summary.ts new file mode 100644 index 0000000000000..0ac9430dcc86d --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/type_summary/print_type_summary.ts @@ -0,0 +1,42 @@ +/* + * 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 { SourceNode } from 'source-map'; + +import { Logger } from '@kbn/type-summarizer-core'; +import { AstIndex } from '../ast_index'; +import { DtsSnipper } from '../dts_snipper'; +import { SourceMapper } from '../source_mapper'; +import { TypeSummaryNamer } from './type_summary_namer'; + +import { printImports } from './print_imports'; +import { printLocals } from './print_locals'; + +/** + * Produces a `SourceNode` which includes the code and source maps for the type summary. To deal + * with naming conflicts a `TypeSummaryNamer` instance is created which will allow the printing + * functions to resolve a `rootSymbol` to a specific name. If a name is not already defines for + * this `rootSymbol` then one is generated for it (generated names are optionally influenced by + * a `hint`). + * + * The result of this function is a `SourceNode` which has functions necessary to produce the + * resulting source code (a .d.ts file) and source map which maps the structurs in the .d.ts file + * to their original source locations in the repository. + */ +export function printTypeSummary( + sourceMaps: SourceMapper, + snipper: DtsSnipper, + log: Logger, + index: AstIndex +) { + const names = new TypeSummaryNamer(index); + const source = new SourceNode(); + printImports(index.imports, names, log, source); + printLocals(index.locals, names, sourceMaps, snipper, log, source); + return source; +} diff --git a/packages/kbn-type-summarizer/src/lib/type_summary/type_summary_namer.ts b/packages/kbn-type-summarizer/src/lib/type_summary/type_summary_namer.ts new file mode 100644 index 0000000000000..8862f9fc3f438 --- /dev/null +++ b/packages/kbn-type-summarizer/src/lib/type_summary/type_summary_namer.ts @@ -0,0 +1,67 @@ +/* + * 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 ts from 'typescript'; + +import { AstIndex } from '../ast_index'; + +const INVALID_NAMES = ['default', 'import', 'export']; + +/** + * Class which is reponsible for managing the list of used names and assigning + * new names to "root symbols". + */ +export class TypeSummaryNamer { + public readonly rootDecsSymbols = new Set(); + private readonly usedNames = new Set(); + private readonly namesBySymbol = new Map(); + + constructor(index: AstIndex) { + for (const ref of index.ambientRefs) { + this.usedNames.add(ref.name); + this.namesBySymbol.set(ref.rootSymbol, ref.name); + } + + for (const l of index.locals) { + this.rootDecsSymbols.add(l.rootSymbol); + if (l.exported?.type === 'named') { + // assign export name to this root symbol, if possible + if (this.usedNames.has(l.exported.name)) { + throw new Error(`multiple exports using the name ${l.exported.name}`); + } + + this.usedNames.add(l.exported.name); + this.namesBySymbol.set(l.rootSymbol, l.exported.name); + } + } + for (const i of index.imports) { + this.rootDecsSymbols.add(i.rootSymbol); + } + } + + get(rootSymbol: ts.Symbol, nameFromSource: string) { + if (!this.rootDecsSymbols.has(rootSymbol)) { + return nameFromSource; + } + + const existing = this.namesBySymbol.get(rootSymbol); + if (existing !== undefined) { + return existing; + } + + let counter = 0; + let name = nameFromSource; + while (this.usedNames.has(name) || INVALID_NAMES.includes(name)) { + name = `${nameFromSource}_${++counter}`; + } + + this.usedNames.add(name); + this.namesBySymbol.set(rootSymbol, name); + return name; + } +} diff --git a/packages/kbn-type-summarizer/src/run_api_extractor.ts b/packages/kbn-type-summarizer/src/run_api_extractor.ts deleted file mode 100644 index 0e7bae5165a4d..0000000000000 --- a/packages/kbn-type-summarizer/src/run_api_extractor.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import Fsp from 'fs/promises'; -import Path from 'path'; - -import { Extractor, ExtractorConfig } from '@microsoft/api-extractor'; - -import { readTsConfigFile } from './lib/tsconfig_file'; -import { CliError } from './lib/cli_error'; - -export async function runApiExtractor( - tsconfigPath: string, - entryPath: string, - dtsBundleOutDir: string -) { - const pkgJson = Path.resolve(Path.dirname(entryPath), 'package.json'); - try { - await Fsp.writeFile( - pkgJson, - JSON.stringify({ - name: 'GENERATED-BY-BAZEL', - description: 'This is a dummy package.json as API Extractor always requires one.', - types: './index.d.ts', - private: true, - license: 'SSPL-1.0 OR Elastic License 2.0', - version: '1.0.0', - }), - { - flag: 'wx', - } - ); - } catch (error) { - if (!error.code || error.code !== 'EEXIST') { - throw error; - } - } - - // API extractor doesn't always support the version of TypeScript used in the repo - // example: at the moment it is not compatable with 3.2 - // to use the internal TypeScript we shall not create a program but rather pass a parsed tsConfig. - const extractorOptions = { - localBuild: false, - }; - - const extractorConfig = ExtractorConfig.prepare({ - configObject: { - compiler: { - overrideTsconfig: readTsConfigFile(tsconfigPath), - }, - projectFolder: Path.dirname(tsconfigPath), - mainEntryPointFilePath: entryPath, - apiReport: { - enabled: false, - // TODO(alan-agius4): remove this folder name when the below issue is solved upstream - // See: https://github.com/microsoft/web-build-tools/issues/1470 - reportFileName: 'invalid', - }, - docModel: { - enabled: false, - }, - dtsRollup: { - enabled: !!dtsBundleOutDir, - untrimmedFilePath: dtsBundleOutDir, - }, - tsdocMetadata: { - enabled: false, - }, - }, - packageJson: undefined, - packageJsonFullPath: pkgJson, - configObjectFullPath: undefined, - }); - const { succeeded } = Extractor.invoke(extractorConfig, extractorOptions); - - if (!succeeded) { - throw new CliError('api-extractor failed'); - } -} diff --git a/packages/kbn-type-summarizer/src/summarize_package.ts b/packages/kbn-type-summarizer/src/summarize_package.ts index d3aac96af1772..7ca85a273b436 100644 --- a/packages/kbn-type-summarizer/src/summarize_package.ts +++ b/packages/kbn-type-summarizer/src/summarize_package.ts @@ -6,18 +6,17 @@ * Side Public License, v 1. */ -import Fsp from 'fs/promises'; -import Path from 'path'; +import { Logger } from '@kbn/type-summarizer-core'; -import normalizePath from 'normalize-path'; - -import { SourceMapper } from './lib/source_mapper'; import { createTsProject } from './lib/ts_project'; import { loadTsConfigFile } from './lib/tsconfig_file'; -import { ExportCollector } from './lib/export_collector'; -import { isNodeModule } from './lib/is_node_module'; -import { Printer } from './lib/printer'; -import { Logger } from './lib/log'; +import { SourceMapper } from './lib/source_mapper'; +import { AstIndexer } from './lib/ast_indexer'; +import { SourceFileMapper } from './lib/source_file_mapper'; +import { SymbolResolver } from './lib/symbol_resolver'; +import { AstTraverser } from './lib/ast_traverser'; +import { printTypeSummary } from './lib/type_summary'; +import { DtsSnipper } from './lib/dts_snipper'; /** * Options used to customize the summarizePackage function @@ -37,87 +36,55 @@ export interface SummarizePacakgeOptions { * array will cause an output .d.ts summary file to be created containing all the AST nodes * which are exported or referenced by those exports. */ - inputPaths: string[]; - /** - * Absolute path to the output directory where the summary .d.ts files should be written - */ - outputDir: string; + inputPath: string; /** - * Repo-relative path to the package source, for example `packages/kbn-type-summarizer` for + * Repo-relative path to the package source, for example `packages/kbn-type-summarizer-core` for * this package. This is used to provide the correct `sourceRoot` path in the resulting source * map files. */ repoRelativePackageDir: string; - /** - * Should the printer throw an error if it doesn't know how to print an AST node? Primarily - * used for testing - */ - strictPrinting?: boolean; } /** * Produce summary .d.ts files for a package */ export async function summarizePackage(log: Logger, options: SummarizePacakgeOptions) { - const tsConfig = loadTsConfigFile(options.tsconfigPath); - log.verbose('Created tsconfig', tsConfig); + const tsConfig = log.step('load config', options.tsconfigPath, () => + loadTsConfigFile(options.tsconfigPath) + ); if (tsConfig.options.sourceRoot) { throw new Error(`${options.tsconfigPath} must not define "compilerOptions.sourceRoot"`); } - const program = createTsProject(tsConfig, options.inputPaths); - log.verbose('Loaded typescript program'); + const program = log.step('create project', options.inputPath, () => + createTsProject(tsConfig, [options.inputPath]) + ); + + const typeChecker = log.step('create type checker', null, () => program.getTypeChecker()); - const typeChecker = program.getTypeChecker(); - log.verbose('Typechecker loaded'); + const sources = new SourceFileMapper(options.dtsDir); + const symbols = new SymbolResolver(typeChecker, log); + const traverse = new AstTraverser(symbols, sources, log); + const indexer = new AstIndexer(typeChecker, sources, symbols, traverse, log); - const sourceFiles = program - .getSourceFiles() - .filter((f) => !isNodeModule(options.dtsDir, f.fileName)) - .sort((a, b) => a.fileName.localeCompare(b.fileName)); + const sourceFile = program.getSourceFile(options.inputPath); + if (!sourceFile) { + throw new Error(`input file wasn't included in the program`); + } - const sourceMapper = await SourceMapper.forSourceFiles( + const index = indexer.indexExports(sourceFile); + const sourceMaps = await SourceMapper.forSourceFiles( log, - options.dtsDir, + sources, options.repoRelativePackageDir, - sourceFiles + program ); - // value that will end up as the `sourceRoot` in the final sourceMaps - const sourceRoot = `../../../${normalizePath(options.repoRelativePackageDir)}`; - - for (const input of options.inputPaths) { - const outputPath = Path.resolve(options.outputDir, Path.basename(input)); - const mapOutputPath = `${outputPath}.map`; - const sourceFile = program.getSourceFile(input); - if (!sourceFile) { - throw new Error(`input file wasn't included in the program`); - } + const snipper = new DtsSnipper(traverse, symbols, log); + const summary = printTypeSummary(sourceMaps, snipper, log, index); - const results = new ExportCollector( - log, - typeChecker, - sourceFile, - options.dtsDir, - sourceMapper - ).run(); + sourceMaps.close(); - const printer = new Printer( - sourceMapper, - results.getAll(), - outputPath, - mapOutputPath, - sourceRoot, - !!options.strictPrinting - ); - - const summary = await printer.print(); - - await Fsp.mkdir(options.outputDir, { recursive: true }); - await Fsp.writeFile(outputPath, summary.code); - await Fsp.writeFile(mapOutputPath, JSON.stringify(summary.map)); - - sourceMapper.close(); - } + return summary; } diff --git a/packages/kbn-type-summarizer/src/tests/integration_helpers.ts b/packages/kbn-type-summarizer/src/tests/integration_helpers.ts index 82ef7c7fbdc4c..ebf88faa7db4c 100644 --- a/packages/kbn-type-summarizer/src/tests/integration_helpers.ts +++ b/packages/kbn-type-summarizer/src/tests/integration_helpers.ts @@ -6,21 +6,31 @@ * Side Public License, v 1. */ -/* eslint-disable no-console */ - import Path from 'path'; import Fsp from 'fs/promises'; import * as ts from 'typescript'; import stripAnsi from 'strip-ansi'; import normalizePath from 'normalize-path'; +import { TestLog } from '@kbn/type-summarizer-core'; import { loadTsConfigFile } from '../lib/tsconfig_file'; import { createTsProject } from '../lib/ts_project'; -import { TestLog } from '../lib/log'; import { summarizePackage } from '../summarize_package'; +import { SourceFileMapper } from '../lib/source_file_mapper'; +import { AstIndexer } from '../lib/ast_indexer'; +import { SymbolResolver } from '../lib/symbol_resolver'; +import { AstTraverser } from '../lib/ast_traverser'; +import { DtsSnipper } from '../lib/dts_snipper'; +import { SourceMapReader } from './source_map_reader'; + +type DiagFilter = (msg: string) => boolean; + +interface InitOptions { + ignoreDiags?: DiagFilter; +} -const TMP_DIR = Path.resolve(__dirname, '../../__tmp__'); +export const TMP_DIR = Path.resolve(__dirname, '../../__tmp__'); const DIAGNOSTIC_HOST = { getCanonicalFileName: (p: string) => p, @@ -47,80 +57,43 @@ function ensureDts(path: string) { return `${path.slice(0, -3)}.d.ts`; } -interface Options { - /* Other files which should be available to the test execution */ - otherFiles?: Record; -} - -class MockCli { - /* file contents which will be fed into TypeScript for this test */ - public readonly mockFiles: Record; - +export class TestProject { /* directory where mockFiles pretend to be from */ - public readonly sourceDir = Path.resolve(TMP_DIR, 'src'); + private readonly sourceDir = Path.resolve(TMP_DIR, 'src'); /* directory where we will write .d.ts versions of mockFiles */ - public readonly dtsOutputDir = Path.resolve(TMP_DIR, 'dist_dts'); - /* directory where output will be written */ - public readonly outputDir = Path.resolve(TMP_DIR, 'dts'); + private readonly dtsOutputDir = Path.resolve(TMP_DIR, 'dist_dts'); /* path where the tsconfig.json file will be written */ - public readonly tsconfigPath = Path.resolve(this.sourceDir, 'tsconfig.json'); + private readonly tsconfigPath = Path.resolve(this.sourceDir, 'tsconfig.json'); /* .d.ts file which we will read to discover the types we need to summarize */ - public readonly inputPath = ensureDts(Path.resolve(this.dtsOutputDir, 'index.ts')); - /* the location we will write the summarized .d.ts file */ - public readonly outputPath = Path.resolve(this.outputDir, Path.basename(this.inputPath)); - /* the location we will write the sourcemaps for the summaried .d.ts file */ - public readonly mapOutputPath = `${this.outputPath}.map`; - - constructor(tsContent: string, options?: Options) { - this.mockFiles = { - ...options?.otherFiles, - 'index.ts': tsContent, - }; - } + private readonly inputPath = ensureDts(Path.resolve(this.dtsOutputDir, 'index.ts')); - private buildDts() { - const program = createTsProject( - loadTsConfigFile(this.tsconfigPath), - Object.keys(this.mockFiles).map((n) => Path.resolve(this.sourceDir, n)) - ); + private readonly log = new TestLog(); - this.printDiagnostics(`dts/config`, program.getConfigFileParsingDiagnostics()); - this.printDiagnostics(`dts/global`, program.getGlobalDiagnostics()); - this.printDiagnostics(`dts/options`, program.getOptionsDiagnostics()); - this.printDiagnostics(`dts/semantic`, program.getSemanticDiagnostics()); - this.printDiagnostics(`dts/syntactic`, program.getSyntacticDiagnostics()); - this.printDiagnostics(`dts/declaration`, program.getDeclarationDiagnostics()); + constructor( + /* file contents which will be fed into TypeScript for this test */ + private readonly _mockFiles: Record + ) {} - const result = program.emit(undefined, undefined, undefined, true); - this.printDiagnostics('dts/results', result.diagnostics); + private *mockFiles() { + for (const [key, value] of Object.entries(this._mockFiles)) { + yield [key, value] as [FileName, string]; + } } - private printDiagnostics(type: string, diagnostics: readonly ts.Diagnostic[]) { - const errors = diagnostics.filter((d) => d.category === ts.DiagnosticCategory.Error); - if (!errors.length) { - return; + private *fileRels() { + for (const key of Object.keys(this._mockFiles)) { + yield key as FileName; } - - const message = ts.formatDiagnosticsWithColorAndContext(errors, DIAGNOSTIC_HOST); - - console.error( - `TS Errors (${type}):\n${message - .split('\n') - .map((l) => ` ${l}`) - .join('\n')}` - ); } - async run() { - const log = new TestLog('debug'); - - // wipe out the tmp dir - await Fsp.rm(TMP_DIR, { recursive: true, force: true }); - + /** + * Initialize the TMP_DIR and write files to the sourceDir + */ + private async setupTempDir() { // write mock files to the filesystem await Promise.all( - Object.entries(this.mockFiles).map(async ([rel, content]) => { + Array.from(this.mockFiles()).map(async ([rel, content]) => { const path = Path.resolve(this.sourceDir, rel); await Fsp.mkdir(Path.dirname(path), { recursive: true }); await Fsp.writeFile(path, dedent(content)); @@ -143,44 +116,137 @@ class MockCli { emitDeclarationOnly: true, declarationDir: '../dist_dts', declarationMap: true, + types: ['node'], // prevent loading all @types packages typeRoots: [], }, }) ); + } + + /** + * convert the source files in the sourceDir to .d.ts files in the dtrOutputDir + */ + private async buildDtsOutput(ignoreDiags?: DiagFilter) { + const program = createTsProject( + loadTsConfigFile(this.tsconfigPath), + Array.from(this.fileRels()) + .map((n) => Path.resolve(this.sourceDir, n)) + .filter((p) => p.endsWith('.ts') || p.endsWith('.tsx')) + ); - // convert the source files to .d.ts files - this.buildDts(); + this.printDiagnostics( + [ + [`dts/config`, program.getConfigFileParsingDiagnostics()], + [`dts/global`, program.getGlobalDiagnostics()], + [`dts/options`, program.getOptionsDiagnostics()], + [`dts/semantic`, program.getSemanticDiagnostics()], + [`dts/syntactic`, program.getSyntacticDiagnostics()], + [`dts/declaration`, program.getDeclarationDiagnostics()], + ], + ignoreDiags + ); + + const result = program.emit(undefined, undefined, undefined, true); + + this.printDiagnostics([['dts/results', result.diagnostics]], ignoreDiags); // copy .d.ts files from source to dist - for (const [rel, content] of Object.entries(this.mockFiles)) { + for (const [rel, content] of this.mockFiles()) { if (rel.endsWith('.d.ts')) { const path = Path.resolve(this.dtsOutputDir, rel); await Fsp.mkdir(Path.dirname(path), { recursive: true }); - await Fsp.writeFile(path, dedent(content)); + await Fsp.writeFile(path, dedent(content as string)); } } + } + + /** + * Print diagnostics from TS so we know when something is wrong in the tests + */ + private printDiagnostics( + types: Array<[type: string, diagnostics: readonly ts.Diagnostic[]]>, + ignoreDiags?: DiagFilter + ) { + const messages = []; + for (const [type, diagnostics] of types) { + const errors = diagnostics.filter((d) => d.category === ts.DiagnosticCategory.Error); + if (!errors.length) { + continue; + } + + const message = ts.formatDiagnosticsWithColorAndContext(errors, DIAGNOSTIC_HOST); + if (ignoreDiags && ignoreDiags(message)) { + continue; + } + messages.push( + ` type(${type}):\n${message + .split('\n') + .map((l) => ` ${l}`) + .join('\n')}` + ); + } + + if (messages.length) { + throw new Error(`TS produced error diagnostics:\n${messages}`); + } + } + + async runTypeSummarizer() { + await this.setupTempDir(); + await this.buildDtsOutput(); // summarize the .d.ts files into the output dir - await summarizePackage(log, { + const sourceNode = await summarizePackage(this.log, { dtsDir: normalizePath(this.dtsOutputDir), - inputPaths: [normalizePath(this.inputPath)], - outputDir: normalizePath(this.outputDir), + inputPath: normalizePath(this.inputPath), repoRelativePackageDir: 'src', tsconfigPath: normalizePath(this.tsconfigPath), - strictPrinting: true, }); + const { map, code } = sourceNode.toStringWithSourceMap(); + // return the results return { - code: await Fsp.readFile(this.outputPath, 'utf8'), - map: JSON.parse(await Fsp.readFile(this.mapOutputPath, 'utf8')), - logs: stripAnsi(log.messages.join('')), + code, + map: await SourceMapReader.snapshot(map, code, this.sourceDir), + logs: stripAnsi(this.log.messages.splice(0).join('')), }; } -} -export async function run(tsContent: string, options?: Options) { - const project = new MockCli(tsContent, options); - return await project.run(); + async initAstIndexer(options?: InitOptions) { + await this.setupTempDir(); + await this.buildDtsOutput(options?.ignoreDiags); + + const tsConfig = loadTsConfigFile(this.tsconfigPath); + const program = createTsProject(tsConfig, [this.inputPath]); + const typeChecker = program.getTypeChecker(); + const sources = new SourceFileMapper(this.dtsOutputDir); + const symbols = new SymbolResolver(typeChecker, this.log); + const traverse = new AstTraverser(symbols, sources, this.log); + const indexer = new AstIndexer(typeChecker, sources, symbols, traverse, this.log); + const snipper = new DtsSnipper(traverse, symbols, this.log); + + const sourceFiles = Object.fromEntries( + Array.from(this.fileRels()).map((rel) => [ + rel, + program.getSourceFile(Path.resolve(this.dtsOutputDir, this.getDtsRel(rel)))!, + ]) + ) as Record; + + return { program, typeChecker, indexer, sourceFiles, snipper }; + } + + private getDtsRel(rel: string) { + if (!rel.endsWith('.d.ts') && rel.endsWith('.ts')) { + return `${rel.slice(0, -3)}.d.ts`; + } + + return rel; + } + + async cleanup() { + // wipe out the tmp dir + await Fsp.rm(TMP_DIR, { recursive: true, force: true }); + } } diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/ast_indexer.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/ast_indexer.test.ts new file mode 100644 index 0000000000000..c453dd2e90c08 --- /dev/null +++ b/packages/kbn-type-summarizer/src/tests/integration_tests/ast_indexer.test.ts @@ -0,0 +1,543 @@ +/* + * 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 ts from 'typescript'; +import { createRecursiveSerializer } from '@kbn/jest-serializers'; +import { describeNode, describeSymbol } from '@kbn/type-summarizer-core'; +import { TestProject, TMP_DIR } from '../integration_helpers'; + +const isObj = (v: any): v is Record => typeof v === 'object' && v !== null; + +expect.addSnapshotSerializer( + createRecursiveSerializer( + (v) => isObj(v) && typeof v.kind === 'number' && ts.SyntaxKind[v.kind] !== undefined, + (v: ts.Node, printRaw) => printRaw(describeNode(v, TMP_DIR)) + ) +); +expect.addSnapshotSerializer( + createRecursiveSerializer( + (v) => isObj(v) && Array.isArray(v.declarations), + (v: ts.Symbol, printRaw) => printRaw(describeSymbol(v, TMP_DIR)) + ) +); + +describe('indexExports()', () => { + describe('simple', () => { + const project = new TestProject({ + 'index.ts': ` + import { Foo } from './foo'; + export { Foo } + export { Bar } from './bar'; + import { Bar } from './bar'; + import { libFn } from 'lib'; + export { libFn } from 'lib'; + import * as A from './a' + export { A } + export type B = Foo | Bar | typeof libFn; + `, + 'foo.ts': ` + export class Foo {} + `, + 'bar.ts': ` + import { Foo } from './foo' + export class Bar extends Foo {} + `, + 'a.ts': ` + export const a = 'a'; + `, + 'node_modules/lib/index.ts': ` + export function libFn() { + } + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('produces valid index', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [], + "imports": Array [ + Object { + "details": Object { + "node": ts.ExportSpecifier (libFn) @ dist_dts/index.d.ts:6:10, + "req": "lib", + "sourceName": "libFn", + "type": "named", + "typesOnly": false, + }, + "exports": Array [ + Object { + "name": "libFn", + "type": "named", + "typeOnly": false, + }, + ], + "localUsageCount": 1, + "rootSymbol": Symbol(ts.FunctionDeclaration (libFn) @ dist_dts/node_modules/lib/index.d.ts:1:1), + "type": "imported decs", + }, + ], + "locals": Array [ + Object { + "decs": Array [ + ts.ClassDeclaration (Foo) @ dist_dts/foo.d.ts:1:1, + ], + "exported": Object { + "name": "Foo", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.ClassDeclaration (Foo) @ dist_dts/foo.d.ts:1:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.ClassDeclaration (Bar) @ dist_dts/bar.d.ts:2:1, + ], + "exported": Object { + "name": "Bar", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.ClassDeclaration (Bar) @ dist_dts/bar.d.ts:2:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.VariableDeclaration (a) @ dist_dts/a.d.ts:1:22, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.VariableDeclaration (a) @ dist_dts/a.d.ts:1:22), + "type": "copied decs", + }, + Object { + "exported": Object { + "name": "A", + "type": "named", + "typeOnly": false, + }, + "members": Map { + "a" => Symbol(ts.VariableDeclaration (a) @ dist_dts/a.d.ts:1:22), + }, + "rootSymbol": Symbol(ts.SourceFile @ dist_dts/a.d.ts:1:1), + "sourceFile": ts.SourceFile @ dist_dts/a.d.ts:1:1, + "type": "namespace dec", + }, + Object { + "decs": Array [ + ts.TypeAliasDeclaration (B) @ dist_dts/index.d.ts:9:1, + ], + "exported": Object { + "name": "B", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.TypeAliasDeclaration (B) @ dist_dts/index.d.ts:9:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('export references', () => { + const project = new TestProject({ + 'index.ts': ` + import type {Class} from './foo' + export function name(i: Class) { + return 'string' + } + `, + 'foo.ts': ` + export class Class {} + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('includes referenced declarations in locals', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [], + "imports": Array [], + "locals": Array [ + Object { + "decs": Array [ + ts.ClassDeclaration (Class) @ dist_dts/foo.d.ts:1:1, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.ClassDeclaration (Class) @ dist_dts/foo.d.ts:1:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.FunctionDeclaration (name) @ dist_dts/index.d.ts:2:1, + ], + "exported": Object { + "name": "name", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.FunctionDeclaration (name) @ dist_dts/index.d.ts:2:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('ambient types', () => { + const project = new TestProject({ + 'index.ts': ` + import './globals' + import './fakemodule' + import { FakeImport } from 'foo' + export async function x(a: SomeGlobal, b: FakeImport): Promise { + return 'foo' + } + `, + 'globals.d.ts': ` + interface SomeGlobal { + foo: true + } + `, + 'fakemodule.d.ts': ` + declare module "foo" { + export interface FakeImport { + bar: true + } + } + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('includes declarations for local ambient types, "ambientRefs" for globals', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [ + Object { + "name": "Promise", + "rootSymbol": Symbol(ts.InterfaceDeclaration (Promise) @ ../../../node_modules/typescript/lib/lib.es5.d.ts:1495:1), + "type": "ambient ref", + }, + ], + "imports": Array [], + "locals": Array [ + Object { + "decs": Array [ + ts.InterfaceDeclaration (SomeGlobal) @ dist_dts/globals.d.ts:1:1, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.InterfaceDeclaration (SomeGlobal) @ dist_dts/globals.d.ts:1:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.InterfaceDeclaration (FakeImport) @ dist_dts/fakemodule.d.ts:2:3, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.InterfaceDeclaration (FakeImport) @ dist_dts/fakemodule.d.ts:2:3), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.FunctionDeclaration (x) @ dist_dts/index.d.ts:4:1, + ], + "exported": Object { + "name": "x", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.FunctionDeclaration (x) @ dist_dts/index.d.ts:4:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('type only exports', () => { + const project = new TestProject({ + 'index.ts': ` + export * from './foo' + `, + 'foo.ts': ` + class Class {} + export type { Class } + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('exports by value one value is exported twice and either is by value', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [], + "imports": Array [], + "locals": Array [ + Object { + "decs": Array [ + ts.ClassDeclaration (Class) @ dist_dts/foo.d.ts:1:1, + ], + "exported": Object { + "name": "Class", + "type": "named", + "typeOnly": true, + }, + "rootSymbol": Symbol(ts.ClassDeclaration (Class) @ dist_dts/foo.d.ts:1:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('export by type combining', () => { + const project = new TestProject({ + 'index.ts': ` + export * from './foo' + export * from './bar' + `, + 'foo.ts': ` + export { Class } from './class' + `, + 'bar.ts': ` + export type { Class } from './class' + `, + 'class.ts': ` + export class Class {} + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('exports by value one value is exported twice and either is by value', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [], + "imports": Array [], + "locals": Array [ + Object { + "decs": Array [ + ts.ClassDeclaration (Class) @ dist_dts/class.d.ts:1:1, + ], + "exported": Object { + "name": "Class", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.ClassDeclaration (Class) @ dist_dts/class.d.ts:1:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('ignores importTypes from node_modules but resolves local import types', () => { + const project = new TestProject({ + 'index.ts': ` + export function name(n: import('./foo').A): import('bar').Bar { + return 'B' + } + `, + 'foo.ts': ` + export class A {} + `, + 'node_modules/bar/index.ts': ` + export type Bar = string | symbol; + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('exports by value one value is exported twice and either is by value', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [], + "imports": Array [], + "locals": Array [ + Object { + "decs": Array [ + ts.ClassDeclaration (A) @ dist_dts/foo.d.ts:1:1, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.ClassDeclaration (A) @ dist_dts/foo.d.ts:1:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.FunctionDeclaration (name) @ dist_dts/index.d.ts:1:1, + ], + "exported": Object { + "name": "name", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.FunctionDeclaration (name) @ dist_dts/index.d.ts:1:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('finds references in importType.typeArguments', () => { + const project = new TestProject({ + 'index.ts': ` + export function name(n: import('./foo').A>) { + return 'B' + } + `, + 'foo.ts': ` + export class A { + n(x: X) { + return x + } + } + export class B {} + `, + 'node_modules/bar/index.ts': ` + export type Bar = Readonly + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('exports by value one value is exported twice and either is by value', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + const index = indexer.indexExports(sourceFiles['index.ts']); + expect(index).toMatchInlineSnapshot(` + Object { + "ambientRefs": Array [], + "imports": Array [], + "locals": Array [ + Object { + "decs": Array [ + ts.ClassDeclaration (A) @ dist_dts/foo.d.ts:1:1, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.ClassDeclaration (A) @ dist_dts/foo.d.ts:1:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.ClassDeclaration (B) @ dist_dts/foo.d.ts:4:1, + ], + "exported": undefined, + "rootSymbol": Symbol(ts.ClassDeclaration (B) @ dist_dts/foo.d.ts:4:1), + "type": "copied decs", + }, + Object { + "decs": Array [ + ts.FunctionDeclaration (name) @ dist_dts/index.d.ts:1:1, + ], + "exported": Object { + "name": "name", + "type": "named", + "typeOnly": false, + }, + "rootSymbol": Symbol(ts.FunctionDeclaration (name) @ dist_dts/index.d.ts:1:1), + "type": "copied decs", + }, + ], + } + `); + }); + }); + + describe('missing node modules', () => { + const project = new TestProject({ + 'index.ts': ` + export * from './foo' + `, + 'foo.ts': ` + import { BaseClass } from 'missing_node_module' + export class Class extends BaseClass { + foo: true + } + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('throws a helpful error when node_modules are missing', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer({ + ignoreDiags: (msg) => msg.includes(`Cannot find module 'missing_node_module'`), + }); + + expect(() => + indexer.indexExports(sourceFiles['index.ts']) + ).toThrowErrorMatchingInlineSnapshot( + `"unable to find declarations for symbol imported from \\"missing_node_module\\". If this is an external module, make sure is it listed in the type dependencies for this package. If it's internal then make sure that TypeScript understands the types of the imported value. Imported: ts.ImportSpecifier (BaseClass) @ packages/kbn-type-summarizer/__tmp__/dist_dts/foo.d.ts:1:10"` + ); + }); + }); + + describe('undeclared symbols', () => { + const project = new TestProject({ + 'index.ts': ` + // @ts-expect-error + export { a } from './foo' + `, + 'foo.js': ` + export function a() {} + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('throws a helpful error when exported symbols are not found', async () => { + const { indexer, sourceFiles } = await project.initAstIndexer(); + + expect(() => + indexer.indexExports(sourceFiles['index.ts']) + ).toThrowErrorMatchingInlineSnapshot( + `"unable to find declarations for symbol imported from \\"./foo\\". If this is an external module, make sure is it listed in the type dependencies for this package. If it's internal then make sure that TypeScript understands the types of the imported value. Imported: ts.ExportSpecifier (a) @ packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts:1:10"` + ); + }); + }); +}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/class.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/class.test.ts deleted file mode 100644 index 0c3c6412269c2..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/class.test.ts +++ /dev/null @@ -1,138 +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 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 { run } from '../integration_helpers'; - -it('prints basic class correctly', async () => { - const output = await run(` - /** - * Interface for writin records to a database - */ - interface Db { - write(record: Record): Promise - } - - export class Foo { - /** - * The name of the Foo - */ - public readonly name: string - constructor(name: string) { - this.name = name.toLowerCase() - } - - speak() { - alert('hi, my name is ' + this.name) - } - - async save(db: Db) { - await db.write({ - name: this.name - }) - } - } - `); - - expect(output.code).toMatchInlineSnapshot(` - "/** - * Interface for writin records to a database - */ - interface Db { - write(record: Record): Promise; - } - export class Foo { - /** - * The name of the Foo - */ - readonly name: string; - constructor(name: string); - speak(): void; - save(db: Db): Promise; - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;;UAGU,E;;;aAIG,G;;;;WAIK,I;;EAKhB,K;EAIM,I", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - debug Ignoring 1 global declarations for \\"Record\\" - debug Ignoring 5 global declarations for \\"Promise\\" - " - `); -}); - -it('prints heritage clauses', async () => { - const output = await run(` - class Foo { - foo() { - return 'foo' - } - } - - interface Named { - name: string - } - - interface Aged { - age: number - } - - export class Bar extends Foo implements Named, Aged { - name = 'bar' - age = 123 - - bar() { - return this.name - } - } - `); - - expect(output.code).toMatchInlineSnapshot(` - "class Foo { - foo(): string; - } - interface Named { - name: string; - } - interface Aged { - age: number; - } - export class Bar extends Foo implements Named, Aged { - name: string; - age: number; - bar(): string; - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "MAAM,G;EACJ,G;;UAKQ,K;;;UAIA,I;;;aAIG,G;EACX,I;EACA,G;EAEA,G", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/dts_snipper.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/dts_snipper.ts new file mode 100644 index 0000000000000..02c8b386cf15a --- /dev/null +++ b/packages/kbn-type-summarizer/src/tests/integration_tests/dts_snipper.ts @@ -0,0 +1,138 @@ +/* + * 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 { TestProject } from '../integration_helpers'; + +describe('toSnippets()', () => { + const project = new TestProject({ + 'index.ts': ` + // tsc drops single-line comments + interface Bar { + name: string + } + + /** + * Class Foo + */ + export class Foo { + /** + * Creates a bar + */ + bar(name: string): Bar { + return { name } + } + } + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('produces source, export, and id snippets', async () => { + const { indexer, snipper, sourceFiles } = await project.initAstIndexer(); + + const index = indexer.indexExports(sourceFiles['index.ts']); + const foo = index.locals.find((l) => !!l.exported); + const bar = index.locals.find((l) => !l.exported); + + expect(snipper.toSnippets(foo!.rootSymbol.declarations[0])).toMatchInlineSnapshot(` + Array [ + Object { + "type": "source", + "value": "/** + * Class Foo + */ + ", + }, + Object { + "noExportRequiresDeclare": false, + "type": "export", + }, + Object { + "type": "source", + "value": "declare class ", + }, + Object { + "identifier": ts.Identifier (Foo) @ dist_dts/index.d.ts:7:22, + "rootSymbol": Symbol(ts.ClassDeclaration (Foo) @ dist_dts/index.d.ts:7:1), + "structural": true, + "text": "Foo", + "type": "indentifier", + }, + Object { + "type": "source", + "value": " { + /** + * Creates a bar + */ + ", + }, + Object { + "identifier": ts.Identifier (bar) @ dist_dts/index.d.ts:11:5, + "rootSymbol": Symbol(ts.MethodDeclaration (bar) @ dist_dts/index.d.ts:11:5), + "structural": true, + "text": "bar", + "type": "indentifier", + }, + Object { + "type": "source", + "value": "(name: string): ", + }, + Object { + "identifier": ts.Identifier (Bar) @ dist_dts/index.d.ts:11:24, + "rootSymbol": Symbol(ts.InterfaceDeclaration (Bar) @ dist_dts/index.d.ts:1:1), + "structural": false, + "text": "Bar", + "type": "indentifier", + }, + Object { + "type": "source", + "value": "; + }", + }, + ] + `); + expect(snipper.toSnippets(bar!.rootSymbol.declarations[0])).toMatchInlineSnapshot(` + Array [ + Object { + "noExportRequiresDeclare": false, + "type": "export", + }, + Object { + "type": "source", + "value": "interface ", + }, + Object { + "identifier": ts.Identifier (Bar) @ dist_dts/index.d.ts:1:11, + "rootSymbol": Symbol(ts.InterfaceDeclaration (Bar) @ dist_dts/index.d.ts:1:1), + "structural": true, + "text": "Bar", + "type": "indentifier", + }, + Object { + "type": "source", + "value": " { + ", + }, + Object { + "identifier": ts.Identifier (name) @ dist_dts/index.d.ts:2:5, + "rootSymbol": Symbol(ts.PropertySignature (name) @ dist_dts/index.d.ts:2:5), + "structural": true, + "text": "name", + "type": "indentifier", + }, + Object { + "type": "source", + "value": ": string; + }", + }, + ] + `); + }); +}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/enum.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/enum.test.ts deleted file mode 100644 index bff3e94bdbb82..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/enum.test.ts +++ /dev/null @@ -1,111 +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 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 { run } from '../integration_helpers'; - -it('prints the whole enum, including comments', async () => { - const result = await run(` - /** - * This is an enum - */ - export enum Foo { - /** - * some comment - */ - x, - /** - * some other comment - */ - y, - /** - * some final comment - */ - z = 1, - } - `); - - expect(result.code).toMatchInlineSnapshot(` - "/** - * This is an enum - */ - export declare enum Foo { - /** - * some comment - */ - x = 0, - /** - * some other comment - */ - y = 1, - /** - * some final comment - */ - z = 1 - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(result.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [], - "version": 3, - } - `); - expect(result.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); - -it(`handles export-type'd enums`, async () => { - const result = await run( - ` - export type { Foo } from './foo' - `, - { - otherFiles: { - ['foo.ts']: ` - export enum Foo { - x = 1, - y = 2, - z = 3, - } - `, - }, - } - ); - - expect(result.code).toMatchInlineSnapshot(` - "export declare enum Foo { - x = 1, - y = 2, - z = 3 - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(result.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [], - "version": 3, - } - `); - expect(result.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' - ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/function.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/function.test.ts deleted file mode 100644 index dd1ce7ad1b53b..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/function.test.ts +++ /dev/null @@ -1,124 +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 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 { run } from '../integration_helpers'; - -it('prints the function declaration, including comments', async () => { - const result = await run( - ` - import { Bar } from './bar'; - - /** - * Convert a Bar to a string - */ - export function foo( - /** - * Important comment - */ - name: Bar - ) { - return name.toString(); - } - `, - { - otherFiles: { - 'bar.ts': ` - export class Bar { - constructor( - private value: T - ){} - - toString() { - return this.value.toString() - } - } - `, - }, - } - ); - - expect(result.code).toMatchInlineSnapshot(` - "class Bar { - private value; - constructor(value: T); - toString(): string; - } - /** - * Convert a Bar to a string - */ - export function foo( - /** - * Important comment - */ - name: Bar): string; - //# sourceMappingURL=index.d.ts.map" - `); - expect(result.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "MAAa,G;;;UAED,K;;EAGV,Q;;;;;gBCAc,G", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "bar.ts", - "index.ts", - ], - "version": 3, - } - `); - expect(result.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/bar.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' - ] - " - `); -}); - -it('uses the export name when it is different', async () => { - const output = await run( - ` - export { foo as bar } from './foo'; - `, - { - otherFiles: { - ['foo.ts']: ` - export function foo() { - return 'foo' - } - `, - }, - } - ); - - expect(output.code).toMatchInlineSnapshot(` - "export function bar(): string; - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "gBAAgB,G", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "foo.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' - ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/import_boundary.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/import_boundary.test.ts deleted file mode 100644 index 3d949a8ede6b2..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/import_boundary.test.ts +++ /dev/null @@ -1,245 +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 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 { run } from '../integration_helpers'; - -const nodeModules = { - 'node_modules/foo/index.ts': ` - export class Foo { - render() { - return 'hello' - } - } - `, - 'node_modules/bar/index.ts': ` - export default class Bar { - render() { - return 'hello' - } - } - `, -}; - -it('output links to named import from node modules', async () => { - const output = await run( - ` - import { Foo } from 'foo' - export type ValidName = string | Foo - `, - { otherFiles: nodeModules } - ); - - expect(output.code).toMatchInlineSnapshot(` - "import { Foo } from 'foo'; - - export type ValidName = string | Foo - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;YACY,S", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); - -it('output links to type exports from node modules', async () => { - const output = await run( - ` - export type { Foo } from 'foo' - `, - { - otherFiles: nodeModules, - } - ); - - expect(output.code).toMatchInlineSnapshot(` - "import type { Foo } from 'foo'; - - export type { Foo }; - - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); - -it('output links to default import from node modules', async () => { - const output = await run( - ` - import Bar from 'bar' - export type ValidName = string | Bar - `, - { otherFiles: nodeModules } - ); - - expect(output.code).toMatchInlineSnapshot(` - "import Bar from 'bar'; - - export type ValidName = string | Bar - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;YACY,S", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); - -it('handles symbols with multiple sources in node_modules', async () => { - const output = await run( - ` - export type { Moment } from 'foo'; - `, - { - otherFiles: { - ['node_modules/foo/index.d.ts']: ` - import mo = require('./foo'); - export = mo; - `, - ['node_modules/foo/foo.d.ts']: ` - import mo = require('mo'); - export = mo; - - declare module "mo" { - export interface Moment { - foo(): string - } - } - `, - ['node_modules/mo/index.d.ts']: ` - declare namespace mo { - interface Moment extends Object { - add(amount?: number, unit?: number): Moment; - } - } - - export = mo; - export as namespace mo; - `, - }, - } - ); - - expect(output.code).toMatchInlineSnapshot(` - "import type { Moment } from 'foo'; - - export type { Moment }; - - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); - -it('deduplicates multiple imports to the same type', async () => { - const output = await run( - ` - export { Foo1 } from './foo1'; - export { Foo2 } from './foo2'; - export { Foo3 } from './foo3'; - `, - { - otherFiles: { - ...nodeModules, - ['foo1.ts']: ` - import { Foo } from 'foo'; - export class Foo1 extends Foo {} - `, - ['foo2.ts']: ` - import { Foo } from 'foo'; - export class Foo2 extends Foo {} - `, - ['foo3.ts']: ` - import { Foo } from 'foo'; - export class Foo3 extends Foo {} - `, - }, - } - ); - - expect(output.code).toMatchInlineSnapshot(` - "import { Foo } from 'foo'; - - export class Foo1 extends Foo { - } - export class Foo2 extends Foo { - } - export class Foo3 extends Foo { - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;aACa,I;;aCAA,I;;aCAA,I", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "foo1.ts", - "foo2.ts", - "foo3.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo1.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo2.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo3.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' - ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/interface.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/interface.test.ts deleted file mode 100644 index de0451f908d37..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/interface.test.ts +++ /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 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 { run } from '../integration_helpers'; - -it('prints the whole interface, including comments', async () => { - const result = await run(` - /** - * This is an interface - */ - export interface Foo { - /** - * method - */ - name(): string - - /** - * hello - */ - close(): Promise - } - `); - - expect(result.code).toMatchInlineSnapshot(` - "/** - * This is an interface - */ - export interface Foo { - /** - * method - */ - name(): string; - /** - * hello - */ - close(): Promise; - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(result.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;;iBAGiB,G", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(result.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - debug Ignoring 5 global declarations for \\"Promise\\" - " - `); -}); - -it(`handles export-type'd interfaces`, async () => { - const result = await run( - ` - export type { Foo } from './foo' - `, - { - otherFiles: { - ['foo.ts']: ` - export interface Foo { - name: string - } - `, - }, - } - ); - - expect(result.code).toMatchInlineSnapshot(` - "export interface Foo { - name: string; - } - //# sourceMappingURL=index.d.ts.map" - `); - expect(result.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "iBAAiB,G", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "foo.ts", - ], - "version": 3, - } - `); - expect(result.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' - ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/literals.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/literals.test.ts deleted file mode 100644 index 1fa965a34a4ab..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/literals.test.ts +++ /dev/null @@ -1,41 +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 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 { run } from '../integration_helpers'; - -it('prints literal number types', async () => { - const output = await run(` - export const NUM = 3; - const NUM2 = 4; - export type PoN = Promise; - `); - - expect(output.code).toMatchInlineSnapshot(` - "export const NUM = 3; - declare const NUM2 = 4; - export type PoN = Promise - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "aAAa,G;cACP,I;YACM,G", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - debug Ignoring 5 global declarations for \\"Promise\\" - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/references.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/references.test.ts deleted file mode 100644 index 0a2cc9aaf5857..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/references.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 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 { run } from '../integration_helpers'; - -it('collects references from source files which contribute to result', async () => { - const result = await run( - ` - /// - export type PromiseOfString = Promise<'string'> - export * from './files' - `, - { - otherFiles: { - 'files/index.ts': ` - /// - export type MySymbol = Symbol & { __tag: 'MySymbol' } - export * from './foo' - `, - 'files/foo.ts': ` - /// - interface Props {} - export type MyComponent = React.Component - `, - }, - } - ); - - expect(result.code).toMatchInlineSnapshot(` - "/// - /// - /// - export type PromiseOfString = Promise<'string'> - export type MySymbol = Symbol & { - __tag: 'MySymbol'; - } - interface Props { - } - export type MyComponent = React.Component - //# sourceMappingURL=index.d.ts.map" - `); - expect(result.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;;YACY,e;YCAA,Q;;;UCAF,K;;YACE,W", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - "files/index.ts", - "files/foo.ts", - ], - "version": 3, - } - `); - expect(result.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/files/foo.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/files/index.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' - ] - debug Ignoring 5 global declarations for \\"Promise\\" - debug Ignoring 4 global declarations for \\"Symbol\\" - debug Ignoring 2 global declarations for \\"Component\\" - debug Ignoring 1 global declarations for \\"React\\" - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/summarize_package.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/summarize_package.test.ts new file mode 100644 index 0000000000000..7fd3f7ea83b86 --- /dev/null +++ b/packages/kbn-type-summarizer/src/tests/integration_tests/summarize_package.test.ts @@ -0,0 +1,171 @@ +/* + * 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 { TestProject } from '../integration_helpers'; + +describe('summarizePackage()', () => { + describe('basic example', () => { + const project = new TestProject({ + 'index.ts': ` + export * from './foo' + export * from './bar' + export * from './baz' + import * as Helpers from './helpers' + export { Helpers } + `, + 'foo.ts': ` + export function foo(name: string) { + return \`hello $\{name}\` + } + `, + 'bar.ts': ` + interface Result { + type: 'success' + } + export class Bar { + doWork(): Result { + return { + type: 'success' + } + } + } + `, + 'baz.ts': ` + import { Bar } from './bar' + import { foo } from './foo' + + export class Baz extends Bar { + hello() { + return foo('baz') + } + } + `, + 'helpers.ts': ` + interface Result { + value: K + } + type A = 'a' + export const a = (): A => 'a' + export const b = (): Result => ({ value: a() }) + `, + }); + + afterEach(async () => { + await project.cleanup(); + }); + + it('produces expected type summary', async () => { + const { code, map, logs } = await project.runTypeSummarizer(); + + expect(code).toMatchInlineSnapshot(` + " + declare type A = 'a'; + + declare const a: () => A + + interface Result { + value: K; + } + + declare const b: () => Result + + declare namespace Helpers { + export { + a, + b, + } + } + export {Helpers} + export declare function foo(name: string): string; + + interface Result_1 { + type: 'success'; + } + + export declare class Bar { + doWork(): Result_1; + } + + export declare class Baz extends Bar { + hello(): string; + } + + " + `); + + expect(map.snapshot).toMatchInlineSnapshot(` + "from A @ 2:13 + to A @ helpers.ts:4:5 + + from a @ 4:14 + to a @ helpers.ts:5:13 + + from Result @ 6:10 + to Result @ helpers.ts:1:10 + + from value @ 7:4 + to value @ helpers.ts:2:2 + + from b @ 10:14 + to b @ helpers.ts:6:13 + + from Helpers @ 12:18 + to interface @ helpers.ts:1:0 + + from foo @ 19:24 + to foo @ foo.ts:1:16 + + from Result_1 @ 21:10 + to Result @ bar.ts:1:10 + + from type @ 22:4 + to type @ bar.ts:2:2 + + from Bar @ 25:21 + to Bar @ bar.ts:4:13 + + from doWork @ 26:4 + to doWork @ bar.ts:5:2 + + from Baz @ 29:21 + to Baz @ baz.ts:4:13 + + from hello @ 30:4 + to hello @ baz.ts:5:2" + `); + + expect(logs).toMatchInlineSnapshot(` + "debg > load config -- packages/kbn-type-summarizer/__tmp__/src/tsconfig.json + debg > create project -- packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts + debg > create type checker + debg > indexExports() -- packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts + debg verbose steps: + symbols.toRootSymbol()x23 + traverse.findReferencedIdentifiers()x8 + symbols.getForIdentifier()x6 + indexSymbol()x12 + debg loaded sourcemaps for [ + 'packages/kbn-type-summarizer/__tmp__/dist_dts/bar.d.ts', + 'packages/kbn-type-summarizer/__tmp__/dist_dts/baz.d.ts', + 'packages/kbn-type-summarizer/__tmp__/dist_dts/foo.d.ts', + 'packages/kbn-type-summarizer/__tmp__/dist_dts/helpers.d.ts', + 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' + ] + debg > printImports() -- 0 imports + debg > printLocals() -- 9 decs + debg verbose steps: + traverse.findStructuralIdentifiers()x8 + traverse.findReferencedIdentifiers()x8 + symbols.getForIdentifier()x18 + symbols.toRootSymbol()x18 + snipper.toSnippets()x8 + " + `); + }); + }); +}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/type_alias.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/type_alias.test.ts deleted file mode 100644 index e4c2c6e355467..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/type_alias.test.ts +++ /dev/null @@ -1,81 +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 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 { run } from '../integration_helpers'; - -it('prints basic type alias', async () => { - const output = await run(` - export type Name = 'foo' | string - - function hello(name: Name) { - console.log('hello', name) - } - - hello('john') - `); - - expect(output.code).toMatchInlineSnapshot(` - "export type Name = 'foo' | string - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "YAAY,I", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); - -it(`prints export type'd type alias`, async () => { - const output = await run( - ` - export type { Name } from './name' - `, - { - otherFiles: { - ['name.ts']: ` - export type Name = 'foo'; - `, - }, - } - ); - - expect(output.code).toMatchInlineSnapshot(` - "export type Name = 'foo' - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": "YAAY,I", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "name.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ - 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts', - 'packages/kbn-type-summarizer/__tmp__/dist_dts/name.d.ts' - ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/integration_tests/variables.test.ts b/packages/kbn-type-summarizer/src/tests/integration_tests/variables.test.ts deleted file mode 100644 index a2b47d6471025..0000000000000 --- a/packages/kbn-type-summarizer/src/tests/integration_tests/variables.test.ts +++ /dev/null @@ -1,68 +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 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 { run } from '../integration_helpers'; - -it('prints basic variable exports with sourcemaps', async () => { - const output = await run(` - /** - * What is a type - */ - type Type = 'bar' | 'baz' - - /** some comment */ - export const bar: Type = 'bar' - - export var - /** - * checkout bar - */ - baz: Type = 'baz', - /** - * this is foo - */ - foo: Type = 'bar' - - export let types = [bar, baz, foo] - `); - - expect(output.code).toMatchInlineSnapshot(` - "/** - * What is a type - */ - type Type = 'bar' | 'baz' - /** some comment */ - export const bar: Type; - /** - * checkout bar - */ - export var baz: Type; - /** - * this is foo - */ - export var foo: Type; - export let types: (\\"bar\\" | \\"baz\\")[]; - //# sourceMappingURL=index.d.ts.map" - `); - expect(output.map).toMatchInlineSnapshot(` - Object { - "file": "index.d.ts", - "mappings": ";;;KAGK,I;;aAGQ,G;;;;WAMX,G;;;;WAIA,G;WAES,K", - "names": Array [], - "sourceRoot": "../../../src", - "sources": Array [ - "index.ts", - ], - "version": 3, - } - `); - expect(output.logs).toMatchInlineSnapshot(` - "debug loaded sourcemaps for [ 'packages/kbn-type-summarizer/__tmp__/dist_dts/index.d.ts' ] - " - `); -}); diff --git a/packages/kbn-type-summarizer/src/tests/source_map_reader.ts b/packages/kbn-type-summarizer/src/tests/source_map_reader.ts new file mode 100644 index 0000000000000..bad4fe492ad46 --- /dev/null +++ b/packages/kbn-type-summarizer/src/tests/source_map_reader.ts @@ -0,0 +1,58 @@ +/* + * 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 Fs from 'fs'; +import Path from 'path'; + +import { SourceMapConsumer, SourceMapGenerator } from 'source-map'; + +const ID_RE = /[a-z0-9_]+/i; + +export class SourceMapReader { + static async snapshot(generator: SourceMapGenerator, code: string, sourceDir: string) { + const genLines = ['', ...code.split('\n')]; + const readSource = (p: string) => { + const source = Fs.readFileSync(Path.resolve(sourceDir, p), 'utf8'); + return ['', ...source.split('\n')]; + }; + const getId = (line: string, col: number) => { + return line.slice(col).match(ID_RE)?.[0] ?? line; + }; + + const mappings: string[][] = []; + + await SourceMapConsumer.with(generator.toJSON(), undefined, (map) => { + map.eachMapping((mapping) => { + if ( + (mapping.originalColumn as number | boolean | null) === false || + mapping.originalColumn === null + ) { + // these mappings are just to end the previous mapping, we can drop them + return; + } + + const generatedId = getId(genLines[mapping.generatedLine], mapping.generatedColumn); + const originalId = mapping.source + ? getId(readSource(mapping.source)[mapping.originalLine], mapping.originalColumn) + : null; + + mappings.push([ + `from ${generatedId} @ ${mapping.generatedLine}:${mapping.generatedColumn}`, + `to ${originalId} @ ${mapping.source}:${mapping.originalLine}:${mapping.originalColumn}`, + ]); + }); + }); + + return new SourceMapReader( + mappings.map((g) => g.join('\n')).join('\n\n'), + JSON.stringify(generator, null, 2) + ); + } + + constructor(public readonly snapshot: string, public readonly raw: string) {} +} diff --git a/packages/kbn-type-summarizer/tsconfig.json b/packages/kbn-type-summarizer/tsconfig.json index b3779bdd686ea..789c6b3111115 100644 --- a/packages/kbn-type-summarizer/tsconfig.json +++ b/packages/kbn-type-summarizer/tsconfig.json @@ -3,8 +3,10 @@ "compilerOptions": { "declaration": true, "declarationMap": true, - "emitDeclarationOnly": false, + "emitDeclarationOnly": true, "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/kbn-typed-react-router-config/BUILD.bazel b/packages/kbn-typed-react-router-config/BUILD.bazel index c2a5aa84dbb2d..a976944c065a3 100644 --- a/packages/kbn-typed-react-router-config/BUILD.bazel +++ b/packages/kbn-typed-react-router-config/BUILD.bazel @@ -43,6 +43,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-io-ts-utils:npm_module_types", "@npm//fp-ts", + "@npm//io-ts", "@npm//query-string", "@npm//utility-types", "@npm//@types/history", @@ -82,6 +83,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-typed-react-router-config/tsconfig.json b/packages/kbn-typed-react-router-config/tsconfig.json index 2619b0ff8f9d3..0dba8cfa68dda 100644 --- a/packages/kbn-typed-react-router-config/tsconfig.json +++ b/packages/kbn-typed-react-router-config/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "isolatedModules": true, "outDir": "./target_types", diff --git a/packages/kbn-ui-shared-deps-npm/BUILD.bazel b/packages/kbn-ui-shared-deps-npm/BUILD.bazel index 4ecc0ac5af67e..7dd393b3be3e1 100644 --- a/packages/kbn-ui-shared-deps-npm/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-npm/BUILD.bazel @@ -120,6 +120,7 @@ ts_project( deps = TYPES_DEPS, allow_js = True, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-ui-shared-deps-npm/tsconfig.json b/packages/kbn-ui-shared-deps-npm/tsconfig.json index a8a821708d036..99bd494705998 100644 --- a/packages/kbn-ui-shared-deps-npm/tsconfig.json +++ b/packages/kbn-ui-shared-deps-npm/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", diff --git a/packages/kbn-ui-shared-deps-src/BUILD.bazel b/packages/kbn-ui-shared-deps-src/BUILD.bazel index 886ae043316d1..ba52aec4a060d 100644 --- a/packages/kbn-ui-shared-deps-src/BUILD.bazel +++ b/packages/kbn-ui-shared-deps-src/BUILD.bazel @@ -75,6 +75,7 @@ ts_project( deps = TYPES_DEPS, allow_js = True, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-ui-shared-deps-src/tsconfig.json b/packages/kbn-ui-shared-deps-src/tsconfig.json index a8a821708d036..99bd494705998 100644 --- a/packages/kbn-ui-shared-deps-src/tsconfig.json +++ b/packages/kbn-ui-shared-deps-src/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "src", diff --git a/packages/kbn-ui-theme/BUILD.bazel b/packages/kbn-ui-theme/BUILD.bazel index 5a848ddcc838f..de61a04b7971c 100644 --- a/packages/kbn-ui-theme/BUILD.bazel +++ b/packages/kbn-ui-theme/BUILD.bazel @@ -60,6 +60,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-ui-theme/tsconfig.json b/packages/kbn-ui-theme/tsconfig.json index 0fd9a15b5dbf8..a0d050defc218 100644 --- a/packages/kbn-ui-theme/tsconfig.json +++ b/packages/kbn-ui-theme/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "stripInternal": true, diff --git a/packages/kbn-utility-types-jest/BUILD.bazel b/packages/kbn-utility-types-jest/BUILD.bazel index beb01908abe3e..d5b3e4601cc80 100644 --- a/packages/kbn-utility-types-jest/BUILD.bazel +++ b/packages/kbn-utility-types-jest/BUILD.bazel @@ -48,6 +48,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-utility-types-jest/tsconfig.json b/packages/kbn-utility-types-jest/tsconfig.json index 1d7104a6fc254..1f2445c22460e 100644 --- a/packages/kbn-utility-types-jest/tsconfig.json +++ b/packages/kbn-utility-types-jest/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "./src", diff --git a/packages/kbn-utility-types/BUILD.bazel b/packages/kbn-utility-types/BUILD.bazel index a990350cc4afc..7c178156a3a5e 100644 --- a/packages/kbn-utility-types/BUILD.bazel +++ b/packages/kbn-utility-types/BUILD.bazel @@ -51,6 +51,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-utility-types/tsconfig.json b/packages/kbn-utility-types/tsconfig.json index db0a3994f6ff7..991d52f959988 100644 --- a/packages/kbn-utility-types/tsconfig.json +++ b/packages/kbn-utility-types/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "./target_types", "rootDir": "./src", diff --git a/packages/kbn-utils/BUILD.bazel b/packages/kbn-utils/BUILD.bazel index b60c60af43c25..c992c6aeacb26 100644 --- a/packages/kbn-utils/BUILD.bazel +++ b/packages/kbn-utils/BUILD.bazel @@ -59,6 +59,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/kbn-utils/tsconfig.json b/packages/kbn-utils/tsconfig.json index a2851d55c0a45..bb93370ed412f 100644 --- a/packages/kbn-utils/tsconfig.json +++ b/packages/kbn-utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "types": [ diff --git a/packages/shared-ux/avatar/solution/BUILD.bazel b/packages/shared-ux/avatar/solution/BUILD.bazel index a9aaeb33ecb50..73345f8e039f0 100644 --- a/packages/shared-ux/avatar/solution/BUILD.bazel +++ b/packages/shared-ux/avatar/solution/BUILD.bazel @@ -105,6 +105,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/avatar/solution/tsconfig.json b/packages/shared-ux/avatar/solution/tsconfig.json index 93076efae5d7c..e1a12b136969c 100644 --- a/packages/shared-ux/avatar/solution/tsconfig.json +++ b/packages/shared-ux/avatar/solution/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/button/exit_full_screen/BUILD.bazel b/packages/shared-ux/button/exit_full_screen/BUILD.bazel index 301be2d69467a..92f2aed71c791 100644 --- a/packages/shared-ux/button/exit_full_screen/BUILD.bazel +++ b/packages/shared-ux/button/exit_full_screen/BUILD.bazel @@ -111,6 +111,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/button/exit_full_screen/tsconfig.json b/packages/shared-ux/button/exit_full_screen/tsconfig.json index 4d110e436c3fe..6fa4e4e74d87c 100644 --- a/packages/shared-ux/button/exit_full_screen/tsconfig.json +++ b/packages/shared-ux/button/exit_full_screen/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/button_toolbar/BUILD.bazel b/packages/shared-ux/button_toolbar/BUILD.bazel index 553ac7edb67fa..d318d48f51e8c 100644 --- a/packages/shared-ux/button_toolbar/BUILD.bazel +++ b/packages/shared-ux/button_toolbar/BUILD.bazel @@ -97,6 +97,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/button_toolbar/tsconfig.json b/packages/shared-ux/button_toolbar/tsconfig.json index 384042fcd7bd4..80b4b1882cb33 100644 --- a/packages/shared-ux/button_toolbar/tsconfig.json +++ b/packages/shared-ux/button_toolbar/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/card/no_data/BUILD.bazel b/packages/shared-ux/card/no_data/BUILD.bazel index 7723b0f9d51c5..74d5371803a4f 100644 --- a/packages/shared-ux/card/no_data/BUILD.bazel +++ b/packages/shared-ux/card/no_data/BUILD.bazel @@ -101,6 +101,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/card/no_data/tsconfig.json b/packages/shared-ux/card/no_data/tsconfig.json index 44b240540718d..4047b69757525 100644 --- a/packages/shared-ux/card/no_data/tsconfig.json +++ b/packages/shared-ux/card/no_data/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/link/redirect_app/BUILD.bazel b/packages/shared-ux/link/redirect_app/BUILD.bazel index 189b5049bf479..685a6c261b20c 100644 --- a/packages/shared-ux/link/redirect_app/BUILD.bazel +++ b/packages/shared-ux/link/redirect_app/BUILD.bazel @@ -98,6 +98,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/link/redirect_app/tsconfig.json b/packages/shared-ux/link/redirect_app/tsconfig.json index 93076efae5d7c..e1a12b136969c 100644 --- a/packages/shared-ux/link/redirect_app/tsconfig.json +++ b/packages/shared-ux/link/redirect_app/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/page/analytics_no_data/BUILD.bazel b/packages/shared-ux/page/analytics_no_data/BUILD.bazel index 79c52ab02a973..ae05ab44e5f51 100644 --- a/packages/shared-ux/page/analytics_no_data/BUILD.bazel +++ b/packages/shared-ux/page/analytics_no_data/BUILD.bazel @@ -94,6 +94,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/page/analytics_no_data/tsconfig.json b/packages/shared-ux/page/analytics_no_data/tsconfig.json index 573ad07325100..7f0fddc5da586 100644 --- a/packages/shared-ux/page/analytics_no_data/tsconfig.json +++ b/packages/shared-ux/page/analytics_no_data/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/page/kibana_no_data/BUILD.bazel b/packages/shared-ux/page/kibana_no_data/BUILD.bazel index abecd09b4197d..0f3ea2fe0f9d2 100644 --- a/packages/shared-ux/page/kibana_no_data/BUILD.bazel +++ b/packages/shared-ux/page/kibana_no_data/BUILD.bazel @@ -104,6 +104,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/page/kibana_no_data/tsconfig.json b/packages/shared-ux/page/kibana_no_data/tsconfig.json index 45842fa3da472..73e46a62a0013 100644 --- a/packages/shared-ux/page/kibana_no_data/tsconfig.json +++ b/packages/shared-ux/page/kibana_no_data/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/packages/shared-ux/prompt/no_data_views/BUILD.bazel b/packages/shared-ux/prompt/no_data_views/BUILD.bazel index ff7401ea94fc6..0251d189e2f1e 100644 --- a/packages/shared-ux/prompt/no_data_views/BUILD.bazel +++ b/packages/shared-ux/prompt/no_data_views/BUILD.bazel @@ -107,6 +107,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/packages/shared-ux/prompt/no_data_views/tsconfig.json b/packages/shared-ux/prompt/no_data_views/tsconfig.json index 45842fa3da472..73e46a62a0013 100644 --- a/packages/shared-ux/prompt/no_data_views/tsconfig.json +++ b/packages/shared-ux/prompt/no_data_views/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/scripts/type_summarizer.js b/scripts/type_summarizer.js index cb1b46e941954..8cad17c6443cd 100644 --- a/scripts/type_summarizer.js +++ b/scripts/type_summarizer.js @@ -8,4 +8,4 @@ require('../src/setup_node_env/no_transpilation'); require('source-map-support/register'); -require('@kbn/type-summarizer/target_node/bazel_cli'); +require('@kbn/type-summarizer-cli'); diff --git a/src/core/server/context/container/context.ts b/src/core/server/context/container/context.ts index a2acdf1e5e4cf..6e9670de9925e 100644 --- a/src/core/server/context/container/context.ts +++ b/src/core/server/context/container/context.ts @@ -10,7 +10,7 @@ import { flatten } from 'lodash'; import { ShallowPromise, MaybePromise } from '@kbn/utility-types'; import type { PluginOpaqueId } from '@kbn/core-base-common'; import type { CoreId } from '@kbn/core-base-common-internal'; -import type { RequestHandler, RequestHandlerContext } from '../..'; +import type { RequestHandler, RequestHandlerContextBase } from '../..'; /** * A function that returns a context value for a specific key of given context type. @@ -26,7 +26,7 @@ import type { RequestHandler, RequestHandlerContext } from '../..'; * @public */ export type IContextProvider< - Context extends RequestHandlerContext, + Context extends RequestHandlerContextBase, ContextName extends keyof Context > = ( // context.core will always be available, but plugin contexts are typed as optional @@ -148,7 +148,7 @@ export interface IContextContainer { * @param provider - A {@link IContextProvider} to be called each time a new context is created. * @returns The {@link IContextContainer} for method chaining. */ - registerContext( + registerContext( pluginOpaqueId: PluginOpaqueId, contextName: ContextName, provider: IContextProvider @@ -195,7 +195,7 @@ export class ContextContainer implements IContextContainer { } public registerContext = < - Context extends RequestHandlerContext, + Context extends RequestHandlerContextBase, ContextName extends keyof Context >( source: symbol, @@ -243,7 +243,7 @@ export class ContextContainer implements IContextContainer { const builtContextPromises: Record> = {}; const builtContext = {} as HandlerContextType; - (builtContext as unknown as RequestHandlerContext).resolve = async (keys) => { + (builtContext as unknown as RequestHandlerContextBase).resolve = async (keys) => { const resolved = await Promise.all( keys.map(async (key) => { return [key, await builtContext[key]]; diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index b004e1352da9b..78e1e3548fdaa 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -19,6 +19,7 @@ import { HttpResources, HttpResourcesServiceToolkit } from '../http_resources'; import { InternalCorePreboot, InternalCoreSetup } from '../internal_types'; import { registerBundleRoutes } from './bundle_routes'; import { UiPlugins } from '../plugins'; +import type { InternalCoreAppRequestHandlerContext } from './internal_types'; /** @internal */ interface CommonRoutesParams { @@ -85,7 +86,7 @@ export class CoreApp { private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { const httpSetup = coreSetup.http; - const router = httpSetup.createRouter(''); + const router = httpSetup.createRouter(''); const resources = coreSetup.httpResources.createRegistrar(router); router.get({ path: '/', validate: false }, async (context, req, res) => { diff --git a/src/core/server/core_app/internal_types.ts b/src/core/server/core_app/internal_types.ts new file mode 100644 index 0000000000000..24b9bd4c709aa --- /dev/null +++ b/src/core/server/core_app/internal_types.ts @@ -0,0 +1,28 @@ +/* + * 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 { RequestHandlerContextBase } from '..'; +import type { IRouter } from '../http'; +import type { UiSettingsRequestHandlerContext } from '../ui_settings'; + +/** + * Request handler context used by core's coreApp routes. + * @internal + */ +export interface InternalCoreAppRequestHandlerContext extends RequestHandlerContextBase { + core: Promise<{ + uiSettings: UiSettingsRequestHandlerContext; + }>; +} + +/** + * Router bound to the {@link InternalCoreAppRequestHandlerContext}. + * Used by core's coreApp routes. + * @internal + */ +export type InternalCoreAppRouter = IRouter; diff --git a/src/core/server/core_route_handler_context.ts b/src/core/server/core_route_handler_context.ts index 3106053eb6afa..66c7f8c6d98d9 100644 --- a/src/core/server/core_route_handler_context.ts +++ b/src/core/server/core_route_handler_context.ts @@ -6,131 +6,63 @@ * Side Public License, v 1. */ -// eslint-disable-next-line max-classes-per-file -import { InternalCoreStart } from './internal_types'; -import { KibanaRequest } from './http/router'; -import { SavedObjectsClientContract } from './saved_objects/types'; +import type { InternalCoreStart } from './internal_types'; +import type { KibanaRequest } from './http'; import { - InternalSavedObjectsServiceStart, - ISavedObjectTypeRegistry, - SavedObjectsClientProviderOptions, + CoreSavedObjectsRouteHandlerContext, + SavedObjectsRequestHandlerContext, } from './saved_objects'; -import { InternalElasticsearchServiceStart, IScopedClusterClient } from './elasticsearch'; -import { InternalUiSettingsServiceStart, IUiSettingsClient } from './ui_settings'; -import { DeprecationsClient, InternalDeprecationsServiceStart } from './deprecations'; - -class CoreElasticsearchRouteHandlerContext { - #client?: IScopedClusterClient; - - constructor( - private readonly elasticsearchStart: InternalElasticsearchServiceStart, - private readonly request: KibanaRequest - ) {} - - public get client() { - if (this.#client == null) { - this.#client = this.elasticsearchStart.client.asScoped(this.request); - } - return this.#client; - } -} - -class CoreSavedObjectsRouteHandlerContext { - constructor( - private readonly savedObjectsStart: InternalSavedObjectsServiceStart, - private readonly request: KibanaRequest - ) {} - #scopedSavedObjectsClient?: SavedObjectsClientContract; - #typeRegistry?: ISavedObjectTypeRegistry; - - public get client() { - if (this.#scopedSavedObjectsClient == null) { - this.#scopedSavedObjectsClient = this.savedObjectsStart.getScopedClient(this.request); - } - return this.#scopedSavedObjectsClient; - } - - public get typeRegistry() { - if (this.#typeRegistry == null) { - this.#typeRegistry = this.savedObjectsStart.getTypeRegistry(); - } - return this.#typeRegistry; - } - - public getClient = (options?: SavedObjectsClientProviderOptions) => { - if (!options) return this.client; - return this.savedObjectsStart.getScopedClient(this.request, options); - }; - - public getExporter = (client: SavedObjectsClientContract) => { - return this.savedObjectsStart.createExporter(client); - }; - - public getImporter = (client: SavedObjectsClientContract) => { - return this.savedObjectsStart.createImporter(client); - }; -} - -class CoreUiSettingsRouteHandlerContext { - #client?: IUiSettingsClient; - constructor( - private readonly uiSettingsStart: InternalUiSettingsServiceStart, - private readonly savedObjectsRouterHandlerContext: CoreSavedObjectsRouteHandlerContext - ) {} - - public get client() { - if (this.#client == null) { - this.#client = this.uiSettingsStart.asScopedToClient( - this.savedObjectsRouterHandlerContext.client - ); - } - return this.#client; - } -} - -class CoreDeprecationsRouteHandlerContext { - #client?: DeprecationsClient; - constructor( - private readonly deprecationsStart: InternalDeprecationsServiceStart, - private readonly elasticsearchRouterHandlerContext: CoreElasticsearchRouteHandlerContext, - private readonly savedObjectsRouterHandlerContext: CoreSavedObjectsRouteHandlerContext - ) {} - - public get client() { - if (this.#client == null) { - this.#client = this.deprecationsStart.asScopedToClient( - this.elasticsearchRouterHandlerContext.client, - this.savedObjectsRouterHandlerContext.client - ); - } - return this.#client; - } +import { + CoreElasticsearchRouteHandlerContext, + ElasticsearchRequestHandlerContext, +} from './elasticsearch'; +import { CoreUiSettingsRouteHandlerContext, UiSettingsRequestHandlerContext } from './ui_settings'; +import { + CoreDeprecationsRouteHandlerContext, + DeprecationsRequestHandlerContext, +} from './deprecations'; + +/** + * The `core` context provided to route handler. + * + * Provides the following clients and services: + * - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client + * which uses the credentials of the incoming request + * - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing + * all the registered types. + * - {@link IScopedClusterClient | elasticsearch.client} - Elasticsearch + * data client which uses the credentials of the incoming request + * - {@link IUiSettingsClient | uiSettings.client} - uiSettings client + * which uses the credentials of the incoming request + * @public + */ +export interface CoreRequestHandlerContext { + savedObjects: SavedObjectsRequestHandlerContext; + elasticsearch: ElasticsearchRequestHandlerContext; + uiSettings: UiSettingsRequestHandlerContext; + deprecations: DeprecationsRequestHandlerContext; } -export class CoreRouteHandlerContext { +/** + * The concrete implementation for Core's route handler context. + * + * @internal + */ +export class CoreRouteHandlerContext implements CoreRequestHandlerContext { readonly elasticsearch: CoreElasticsearchRouteHandlerContext; readonly savedObjects: CoreSavedObjectsRouteHandlerContext; readonly uiSettings: CoreUiSettingsRouteHandlerContext; readonly deprecations: CoreDeprecationsRouteHandlerContext; - constructor( - private readonly coreStart: InternalCoreStart, - private readonly request: KibanaRequest - ) { - this.elasticsearch = new CoreElasticsearchRouteHandlerContext( - this.coreStart.elasticsearch, - this.request - ); - this.savedObjects = new CoreSavedObjectsRouteHandlerContext( - this.coreStart.savedObjects, - this.request - ); + constructor(coreStart: InternalCoreStart, request: KibanaRequest) { + this.elasticsearch = new CoreElasticsearchRouteHandlerContext(coreStart.elasticsearch, request); + this.savedObjects = new CoreSavedObjectsRouteHandlerContext(coreStart.savedObjects, request); this.uiSettings = new CoreUiSettingsRouteHandlerContext( - this.coreStart.uiSettings, + coreStart.uiSettings, this.savedObjects ); this.deprecations = new CoreDeprecationsRouteHandlerContext( - this.coreStart.deprecations, + coreStart.deprecations, this.elasticsearch, this.savedObjects ); diff --git a/src/core/server/deprecations/deprecations_route_handler_context.ts b/src/core/server/deprecations/deprecations_route_handler_context.ts new file mode 100644 index 0000000000000..76e5d974056d4 --- /dev/null +++ b/src/core/server/deprecations/deprecations_route_handler_context.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 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 { CoreElasticsearchRouteHandlerContext } from '../elasticsearch'; +import type { CoreSavedObjectsRouteHandlerContext } from '../saved_objects'; +import type { DeprecationsClient, InternalDeprecationsServiceStart } from './deprecations_service'; + +/** + * Core's `deprecations` request handler context. + * @public + */ +export interface DeprecationsRequestHandlerContext { + client: DeprecationsClient; +} + +/** + * The {@link DeprecationsRequestHandlerContext} implementation. + * @internal + */ +export class CoreDeprecationsRouteHandlerContext implements DeprecationsRequestHandlerContext { + #client?: DeprecationsClient; + + constructor( + private readonly deprecationsStart: InternalDeprecationsServiceStart, + private readonly elasticsearchRouterHandlerContext: CoreElasticsearchRouteHandlerContext, + private readonly savedObjectsRouterHandlerContext: CoreSavedObjectsRouteHandlerContext + ) {} + + public get client() { + if (this.#client == null) { + this.#client = this.deprecationsStart.asScopedToClient( + this.elasticsearchRouterHandlerContext.client, + this.savedObjectsRouterHandlerContext.client + ); + } + return this.#client; + } +} diff --git a/src/core/server/deprecations/index.ts b/src/core/server/deprecations/index.ts index d9225750f04a1..9245179d3099a 100644 --- a/src/core/server/deprecations/index.ts +++ b/src/core/server/deprecations/index.ts @@ -25,3 +25,5 @@ export type { export { DeprecationsService } from './deprecations_service'; export { config } from './deprecation_config'; +export { CoreDeprecationsRouteHandlerContext } from './deprecations_route_handler_context'; +export type { DeprecationsRequestHandlerContext } from './deprecations_route_handler_context'; diff --git a/src/core/server/deprecations/internal_types.ts b/src/core/server/deprecations/internal_types.ts new file mode 100644 index 0000000000000..0f23360e94ba9 --- /dev/null +++ b/src/core/server/deprecations/internal_types.ts @@ -0,0 +1,26 @@ +/* + * 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 { IRouter } from '../http'; +import type { RequestHandlerContextBase } from '..'; +import type { DeprecationsRequestHandlerContext } from './deprecations_route_handler_context'; + +/** + * Request handler context used by core's deprecations routes. + * @internal + */ +export interface InternalDeprecationRequestHandlerContext extends RequestHandlerContextBase { + core: Promise<{ deprecations: DeprecationsRequestHandlerContext }>; +} + +/** + * Router bound to the {@link InternalDeprecationRequestHandlerContext}. + * Used by core's deprecations routes. + * @internal + */ +export type InternalDeprecationRouter = IRouter; diff --git a/src/core/server/deprecations/routes/get.ts b/src/core/server/deprecations/routes/get.ts index 88965505488f9..a3ea08f04d706 100644 --- a/src/core/server/deprecations/routes/get.ts +++ b/src/core/server/deprecations/routes/get.ts @@ -5,10 +5,11 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { IRouter } from '../../http'; -import { DeprecationsGetResponse } from '../types'; -export const registerGetRoute = (router: IRouter) => { +import type { DeprecationsGetResponse } from '../types'; +import type { InternalDeprecationRouter } from '../internal_types'; + +export const registerGetRoute = (router: InternalDeprecationRouter) => { router.get( { path: '/', diff --git a/src/core/server/deprecations/routes/index.ts b/src/core/server/deprecations/routes/index.ts index 633950be61d12..5c73af882085c 100644 --- a/src/core/server/deprecations/routes/index.ts +++ b/src/core/server/deprecations/routes/index.ts @@ -7,9 +7,10 @@ */ import { InternalHttpServiceSetup } from '../../http'; +import type { InternalDeprecationRequestHandlerContext } from '../internal_types'; import { registerGetRoute } from './get'; export function registerRoutes({ http }: { http: InternalHttpServiceSetup }) { - const router = http.createRouter('/api/deprecations'); + const router = http.createRouter('/api/deprecations'); registerGetRoute(router); } diff --git a/src/core/server/elasticsearch/client/index.ts b/src/core/server/elasticsearch/client/index.ts index f0f2768e644d4..b1630ec25c9ff 100644 --- a/src/core/server/elasticsearch/client/index.ts +++ b/src/core/server/elasticsearch/client/index.ts @@ -6,15 +6,7 @@ * Side Public License, v 1. */ -export type { - ElasticsearchClient, - ShardsResponse, - ShardsInfo, - CountResponse, - SearchResponse, - GetResponse, - DeleteDocumentResponse, -} from './types'; +export type { ElasticsearchClient } from './types'; export { ScopedClusterClient } from './scoped_cluster_client'; export type { IScopedClusterClient } from './scoped_cluster_client'; export type { ElasticsearchClientConfig } from './client_config'; diff --git a/src/core/server/elasticsearch/client/types.ts b/src/core/server/elasticsearch/client/types.ts index 3f95616425633..e4978bfde1086 100644 --- a/src/core/server/elasticsearch/client/types.ts +++ b/src/core/server/elasticsearch/client/types.ts @@ -17,106 +17,3 @@ export type ElasticsearchClient = Omit< Client, 'connectionPool' | 'serializer' | 'extend' | 'close' | 'diagnostic' >; - -/** - * All response typings are maintained until elasticsearch-js provides them out of the box - * https://github.com/elastic/elasticsearch-js/pull/970 - */ - -/** - * @public - */ -export interface ShardsResponse { - total: number; - successful: number; - failed: number; - skipped: number; -} - -/** - * @public - */ -export interface Explanation { - value: number; - description: string; - details: Explanation[]; -} - -/** - * @public - */ -export interface ShardsInfo { - total: number; - successful: number; - skipped: number; - failed: number; -} - -/** - * @public - */ -export interface CountResponse { - _shards: ShardsInfo; - count: number; -} - -/** - * @public - */ -export interface SearchResponse { - took: number; - timed_out: boolean; - _scroll_id?: string; - _shards: ShardsResponse; - hits: { - total: number; - max_score: number; - hits: Array<{ - _index: string; - _type: string; - _id: string; - _score: number; - _source: T; - _version?: number; - _explanation?: Explanation; - fields?: any; - highlight?: any; - inner_hits?: any; - matched_queries?: string[]; - sort?: unknown[]; - }>; - }; - aggregations?: any; - pit_id?: string; -} - -/** - * @public - */ -export interface GetResponse { - _index: string; - _type: string; - _id: string; - _version: number; - _routing?: string; - found: boolean; - _source: T; - _seq_no: number; - _primary_term: number; -} - -/** - * @public - */ -export interface DeleteDocumentResponse { - _shards: ShardsResponse; - found: boolean; - _index: string; - _type: string; - _id: string; - _version: number; - result: string; - error?: { - type: string; - }; -} diff --git a/src/core/server/elasticsearch/elasticsearch_route_handler_context.ts b/src/core/server/elasticsearch/elasticsearch_route_handler_context.ts new file mode 100644 index 0000000000000..536ce7f369b6e --- /dev/null +++ b/src/core/server/elasticsearch/elasticsearch_route_handler_context.ts @@ -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 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 { KibanaRequest } from '../http'; +import type { IScopedClusterClient } from './client'; +import type { InternalElasticsearchServiceStart } from './types'; + +/** + * Core's `elasticsearch` request handler context. + * @public + */ +export interface ElasticsearchRequestHandlerContext { + client: IScopedClusterClient; +} + +/** + * The {@link UiSettingsRequestHandlerContext} implementation. + * @internal + */ +export class CoreElasticsearchRouteHandlerContext implements ElasticsearchRequestHandlerContext { + #client?: IScopedClusterClient; + + constructor( + private readonly elasticsearchStart: InternalElasticsearchServiceStart, + private readonly request: KibanaRequest + ) {} + + public get client() { + if (this.#client == null) { + this.#client = this.elasticsearchStart.client.asScoped(this.request); + } + return this.#client; + } +} diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index f146ba937509e..b23b12ffe392c 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -31,13 +31,6 @@ export type { ElasticsearchClientConfig, ElasticsearchClient, IScopedClusterClient, - // responses - SearchResponse, - CountResponse, - ShardsInfo, - ShardsResponse, - GetResponse, - DeleteDocumentResponse, // unauthorized error handler UnauthorizedErrorHandlerOptions, UnauthorizedErrorHandlerResultRetryParams, @@ -54,3 +47,5 @@ export { isNotFoundFromUnsupportedServer, PRODUCT_RESPONSE_HEADER, } from './supported_server_response_check'; +export { CoreElasticsearchRouteHandlerContext } from './elasticsearch_route_handler_context'; +export type { ElasticsearchRequestHandlerContext } from './elasticsearch_route_handler_context'; diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index a733bf796f62e..40dd31e8116df 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -8,6 +8,7 @@ import { ExecutionContextContainer } from '@kbn/core-execution-context-browser-internal'; import * as kbnTestServer from '../../../test_helpers/kbn_server'; +import { RequestHandlerContext } from '../..'; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -63,7 +64,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asInternalUser.ping({}, { meta: true }); @@ -86,7 +87,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asCurrentUser.ping({}, { meta: true }); @@ -109,7 +110,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asInternalUser.ping({}, { meta: true }); @@ -128,7 +129,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asCurrentUser.ping({}, { meta: true }); @@ -147,7 +148,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asInternalUser.ping( @@ -194,7 +195,7 @@ describe('trace', () => { const { http } = await rootExecutionContextDisabled.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asCurrentUser.ping({}, { meta: true }); @@ -217,7 +218,7 @@ describe('trace', () => { const { http, executionContext } = await rootExecutionContextDisabled.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set(parentContext); const esClient = (await context.core).elasticsearch.client; @@ -250,7 +251,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set(parentContext); return res.ok({ body: executionContext.get() }); @@ -265,7 +266,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set(parentContext); await delay(100); @@ -281,7 +282,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); let id = 42; router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set({ ...parentContext, id: String(id++) }); @@ -301,7 +302,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); let id = 2; router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set({ ...parentContext, id: String(id) }); @@ -329,7 +330,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); let id = 2; router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set(parentContext); @@ -364,7 +365,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, (context, req, res) => res.ok({ body: executionContext.get() }) ); @@ -382,7 +383,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asCurrentUser.ping({}, { meta: true }); @@ -416,7 +417,7 @@ describe('trace', () => { registerOnPreResponse, } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { return res.ok({ body: executionContext.get()?.toJSON() }); }); @@ -470,7 +471,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asCurrentUser.ping({}, { meta: true }); @@ -492,7 +493,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asInternalUser.ping({}, { meta: true }); @@ -514,7 +515,7 @@ describe('trace', () => { const { http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { const esClient = (await context.core).elasticsearch.client; const { headers } = await esClient.asCurrentUser.ping({}, { meta: true }); @@ -537,7 +538,7 @@ describe('trace', () => { const { http, executionContext } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { executionContext.set(parentContext); const esClient = (await context.core).elasticsearch.client; @@ -561,7 +562,7 @@ describe('trace', () => { const { http, executionContext } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); const ctx = { type: 'test-type', name: 'test-name', @@ -588,7 +589,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); router.get({ path: '/execution-context', validate: false }, async (context, req, res) => { return executionContext.withContext(parentContext, () => res.ok({ body: executionContext.get() }) @@ -604,7 +605,7 @@ describe('trace', () => { const { executionContext, http } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); const nestedContext = { type: 'nested-type', name: 'nested-name', @@ -630,7 +631,7 @@ describe('trace', () => { const { http, executionContext } = await root.setup(); const { createRouter } = http; - const router = createRouter(''); + const router = createRouter(''); const newContext = { type: 'new-type', name: 'new-name', diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 5746bd1d6306e..ac2597a0d28ce 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -25,7 +25,7 @@ import { } from './router'; import { HttpServer } from './http_server'; import { Readable } from 'stream'; -import { RequestHandlerContext } from '..'; +import { RequestHandlerContextBase } from '..'; import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import moment from 'moment'; import { of } from 'rxjs'; @@ -466,7 +466,7 @@ test('not inline handler - KibanaRequest', async () => { const router = new Router('/foo', logger, enhanceWithContext); const handler = ( - context: RequestHandlerContext, + context: RequestHandlerContextBase, req: KibanaRequest, res: KibanaResponseFactory ) => { diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index d3e9fd7fe27f2..c0ddd0ac5b3f4 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -30,6 +30,7 @@ import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; import { OnPreResponseToolkit } from './lifecycle/on_pre_response'; import { ExternalUrlConfig } from './external_url'; import type { IAuthHeadersStorage } from './auth_headers_storage'; +import type { RequestHandlerContextBase } from '..'; type BasePathMocked = jest.Mocked; type AuthMocked = jest.Mocked; @@ -38,9 +39,9 @@ export type HttpServicePrebootMock = jest.Mocked; export type InternalHttpServicePrebootMock = jest.Mocked< Omit > & { basePath: BasePathMocked }; -export type HttpServiceSetupMock = jest.Mocked< - Omit -> & { +export type HttpServiceSetupMock< + ContextType extends RequestHandlerContextBase = RequestHandlerContextBase +> = jest.Mocked, 'basePath' | 'createRouter'>> & { basePath: BasePathMocked; createRouter: jest.MockedFunction<() => RouterMock>; }; @@ -166,10 +167,12 @@ const createInternalSetupContractMock = () => { return mock; }; -const createSetupContractMock = () => { +const createSetupContractMock = < + ContextType extends RequestHandlerContextBase = RequestHandlerContextBase +>() => { const internalMock = createInternalSetupContractMock(); - const mock: HttpServiceSetupMock = { + const mock: HttpServiceSetupMock = { createCookieSessionStorageFactory: internalMock.createCookieSessionStorageFactory, registerOnPreRouting: internalMock.registerOnPreRouting, registerOnPreAuth: jest.fn(), diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index f13a0fbaf294d..1014b89e7425e 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -16,11 +16,11 @@ import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { PluginOpaqueId } from '@kbn/core-base-common'; import type { InternalExecutionContextSetup } from '@kbn/core-execution-context-server-internal'; -import type { RequestHandlerContext } from '..'; +import type { RequestHandlerContextBase } from '..'; import { InternalContextSetup, InternalContextPreboot } from '../context'; import { CspConfigType, cspConfig } from './csp'; -import { Router } from './router'; +import { Router, IRouter } from './router'; import { HttpConfig, HttpConfigType, config as httpConfig } from './http_config'; import { HttpServer } from './http_server'; import { HttpsRedirectServer } from './https_redirect_server'; @@ -114,8 +114,13 @@ export class HttpService server: prebootSetup.server, registerRouteHandlerContext: (pluginOpaqueId, contextName, provider) => prebootServerRequestHandlerContext.registerContext(pluginOpaqueId, contextName, provider), - registerRoutes: (path, registerCallback) => { - const router = new Router( + registerRoutes: < + DefaultRequestHandlerType extends RequestHandlerContextBase = RequestHandlerContextBase + >( + path: string, + registerCallback: (router: IRouter) => void + ) => { + const router = new Router( path, this.log, prebootServerRequestHandlerContext.createHandler.bind(null, this.coreContext.coreId) @@ -157,7 +162,7 @@ export class HttpService externalUrl: new ExternalUrlConfig(config.externalUrl), - createRouter: ( + createRouter: ( path: string, pluginId: PluginOpaqueId = this.coreContext.coreId ) => { @@ -168,7 +173,7 @@ export class HttpService }, registerRouteHandlerContext: < - Context extends RequestHandlerContext, + Context extends RequestHandlerContextBase, ContextName extends keyof Context >( pluginOpaqueId: PluginOpaqueId, diff --git a/src/core/server/http/router/error_wrapper.test.ts b/src/core/server/http/router/error_wrapper.test.ts index 948aff8f32fee..48dad17fcf934 100644 --- a/src/core/server/http/router/error_wrapper.test.ts +++ b/src/core/server/http/router/error_wrapper.test.ts @@ -9,7 +9,8 @@ import Boom from '@hapi/boom'; import { KibanaResponse, KibanaResponseFactory, kibanaResponseFactory } from './response'; import { wrapErrors } from './error_wrapper'; -import { KibanaRequest, RequestHandler, RequestHandlerContext } from '../..'; +import { RequestHandlerContextBase } from '../..'; +import { KibanaRequest, RequestHandler } from '..'; const createHandler = (handler: () => any): RequestHandler => @@ -18,7 +19,7 @@ const createHandler = }; describe('wrapErrors', () => { - let context: RequestHandlerContext; + let context: RequestHandlerContextBase; let request: KibanaRequest; let response: KibanaResponseFactory; diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index 59611e51d9a15..570f4f973dd53 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -24,7 +24,7 @@ import { } from './response'; import { RouteConfig, RouteConfigOptions, RouteMethod, validBodyOutput } from './route'; import { HapiResponseAdapter } from './response_adapter'; -import { RequestHandlerContext } from '../..'; +import { RequestHandlerContextBase } from '../..'; import { wrapErrors } from './error_wrapper'; import { RouteValidator } from './validator'; @@ -46,7 +46,7 @@ export interface RouterRoute { */ export type RouteRegistrar< Method extends RouteMethod, - Context extends RequestHandlerContext = RequestHandlerContext + Context extends RequestHandlerContextBase = RequestHandlerContextBase > = ( route: RouteConfig, handler: RequestHandler @@ -58,7 +58,7 @@ export type RouteRegistrar< * * @public */ -export interface IRouter { +export interface IRouter { /** * Resulted path */ @@ -118,7 +118,7 @@ export type ContextEnhancer< Q, B, Method extends RouteMethod, - Context extends RequestHandlerContext + Context extends RequestHandlerContextBase > = (handler: RequestHandler) => RequestHandlerEnhanced; function getRouteFullPath(routerPath: string, routePath: string) { @@ -202,7 +202,7 @@ function validOptions( /** * @internal */ -export class Router +export class Router implements IRouter { public routes: Array> = []; @@ -307,7 +307,7 @@ type WithoutHeadArgument = T extends (first: any, ...rest: infer Params) => i : never; type RequestHandlerEnhanced = WithoutHeadArgument< - RequestHandler + RequestHandler >; /** @@ -350,7 +350,7 @@ export type RequestHandler< P = unknown, Q = unknown, B = unknown, - Context extends RequestHandlerContext = RequestHandlerContext, + Context extends RequestHandlerContextBase = RequestHandlerContextBase, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory > = ( @@ -376,7 +376,7 @@ export type RequestHandlerWrapper = < P, Q, B, - Context extends RequestHandlerContext = RequestHandlerContext, + Context extends RequestHandlerContextBase = RequestHandlerContextBase, Method extends RouteMethod = any, ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory >( diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 55bef12fd0f8b..46b74deec2bc2 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -21,7 +21,7 @@ import { OnPostAuthHandler } from './lifecycle/on_post_auth'; import { OnPreResponseHandler } from './lifecycle/on_pre_response'; import { IBasePath } from './base_path_service'; import { ExternalUrlConfig } from './external_url'; -import type { PluginOpaqueId, RequestHandlerContext } from '..'; +import type { PluginOpaqueId, RequestHandlerContextBase } from '..'; /** * An object that handles registration of http request context providers. @@ -36,7 +36,7 @@ export type RequestHandlerContextContainer = IContextContainer; * @public */ export type RequestHandlerContextProvider< - Context extends RequestHandlerContext, + Context extends RequestHandlerContextBase, ContextName extends keyof Context > = IContextProvider; @@ -117,7 +117,9 @@ export interface HttpAuth { * ``` * @public */ -export interface HttpServicePreboot { +export interface HttpServicePreboot< + DefaultRequestHandlerType extends RequestHandlerContextBase = RequestHandlerContextBase +> { /** * Provides ability to acquire `preboot` {@link IRouter} instance for a particular top-level path and register handler * functions for any number of nested routes. @@ -135,7 +137,10 @@ export interface HttpServicePreboot { * ``` * @public */ - registerRoutes(path: string, callback: (router: IRouter) => void): void; + registerRoutes( + path: string, + callback: (router: IRouter) => void + ): void; /** * Access or manipulate the Kibana base path @@ -162,7 +167,12 @@ export interface InternalHttpServicePreboot | 'server' | 'getServerInfo' > { - registerRoutes(path: string, callback: (router: IRouter) => void): void; + registerRoutes< + DefaultRequestHandlerType extends RequestHandlerContextBase = RequestHandlerContextBase + >( + path: string, + callback: (router: IRouter) => void + ): void; } /** @@ -237,7 +247,9 @@ export interface InternalHttpServicePreboot * ``` * @public */ -export interface HttpServiceSetup { +export interface HttpServiceSetup< + DefaultRequestHandlerType extends RequestHandlerContextBase = RequestHandlerContextBase +> { /** * Creates cookie based session storage factory {@link SessionStorageFactory} * @param cookieOptions {@link SessionStorageCookieOptions} - options to configure created cookie session storage. @@ -333,7 +345,7 @@ export interface HttpServiceSetup { * @public */ createRouter: < - Context extends RequestHandlerContext = RequestHandlerContext + Context extends DefaultRequestHandlerType = DefaultRequestHandlerType >() => IRouter; /** @@ -365,7 +377,7 @@ export interface HttpServiceSetup { * @public */ registerRouteHandlerContext: < - Context extends RequestHandlerContext, + Context extends DefaultRequestHandlerType, ContextName extends keyof Omit >( contextName: ContextName, @@ -384,7 +396,7 @@ export interface InternalHttpServiceSetup auth: HttpServerSetup['auth']; server: HttpServerSetup['server']; externalUrl: ExternalUrlConfig; - createRouter: ( + createRouter: ( path: string, plugin?: PluginOpaqueId ) => IRouter; @@ -392,7 +404,7 @@ export interface InternalHttpServiceSetup registerStaticDir: (path: string, dirPath: string) => void; authRequestHeaders: IAuthHeadersStorage; registerRouteHandlerContext: < - Context extends RequestHandlerContext, + Context extends RequestHandlerContextBase, ContextName extends keyof Omit >( pluginOpaqueId: PluginOpaqueId, diff --git a/src/core/server/http_resources/http_resources_service.test.ts b/src/core/server/http_resources/http_resources_service.test.ts index 91500737123c7..e626d496c8dec 100644 --- a/src/core/server/http_resources/http_resources_service.test.ts +++ b/src/core/server/http_resources/http_resources_service.test.ts @@ -8,7 +8,7 @@ import { getApmConfigMock } from './http_resources_service.test.mocks'; -import { IRouter, RouteConfig } from '../http'; +import { RouteConfig } from '../http'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { coreMock } from '../mocks'; @@ -25,7 +25,7 @@ describe('HttpResources service', () => { let service: HttpResourcesService; let prebootDeps: PrebootDeps; let setupDeps: SetupDeps; - let router: jest.Mocked; + let router: ReturnType; const kibanaRequest = httpServerMock.createKibanaRequest(); const context = coreMock.createCustomRequestHandlerContext({}); const apmConfig = { mockApmConfig: true }; diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts index 978ad8e72621b..2fa90b4346965 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/src/core/server/http_resources/http_resources_service.ts @@ -63,7 +63,10 @@ export class HttpResourcesService implements CoreService + ): HttpResources { return { register: ( route: RouteConfig, diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 25dd89acab6e6..1f1bdaeab57f0 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -46,29 +46,26 @@ import { ElasticsearchServiceSetup, configSchema as elasticsearchConfigSchema, ElasticsearchServiceStart, - IScopedClusterClient, ElasticsearchServicePreboot, } from './elasticsearch'; -import { HttpServicePreboot, HttpServiceSetup, HttpServiceStart } from './http'; +import type { + HttpServicePreboot, + HttpServiceSetup, + HttpServiceStart, + IRouter, + RequestHandler, +} from './http'; import { HttpResources } from './http_resources'; import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; -import { IUiSettingsClient, UiSettingsServiceSetup, UiSettingsServiceStart } from './ui_settings'; -import { SavedObjectsClientContract } from './saved_objects/types'; -import { - ISavedObjectTypeRegistry, - SavedObjectsServiceSetup, - SavedObjectsServiceStart, - ISavedObjectsExporter, - ISavedObjectsImporter, - SavedObjectsClientProviderOptions, -} from './saved_objects'; +import { UiSettingsServiceSetup, UiSettingsServiceStart } from './ui_settings'; +import { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './saved_objects'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { MetricsServiceSetup, MetricsServiceStart } from './metrics'; import { StatusServiceSetup } from './status'; import { CoreUsageDataStart, CoreUsageDataSetup } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; -import { DeprecationsServiceSetup, DeprecationsClient } from './deprecations'; +import { DeprecationsServiceSetup } from './deprecations'; // Because of #79265 we need to explicitly import, then export these types for // scripts/telemetry_check.js to work as expected import { @@ -80,6 +77,9 @@ import { CoreServicesUsageData, } from './core_usage_data'; import { PrebootServicePreboot } from './preboot'; +import type { CoreRequestHandlerContext } from './core_route_handler_context'; +import type { PrebootCoreRequestHandlerContext } from './preboot_core_route_handler_context'; +import { KibanaResponseFactory, RouteMethod } from './http'; export type { PrebootServicePreboot } from './preboot'; @@ -136,12 +136,6 @@ export type { ICustomClusterClient, ElasticsearchClientConfig, IScopedClusterClient, - SearchResponse, - CountResponse, - ShardsInfo, - ShardsResponse, - GetResponse, - DeleteDocumentResponse, ElasticsearchConfigPreboot, PollEsNodesVersionOptions, UnauthorizedErrorHandlerOptions, @@ -151,6 +145,7 @@ export type { UnauthorizedErrorHandlerResult, UnauthorizedErrorHandlerToolkit, UnauthorizedErrorHandler, + ElasticsearchRequestHandlerContext, } from './elasticsearch'; export type { @@ -176,7 +171,6 @@ export type { HttpResponsePayload, HttpServerInfo, HttpServicePreboot, - HttpServiceSetup, HttpServiceStart, ErrorHttpResponseOptions, IKibanaSocket, @@ -199,7 +193,6 @@ export type { OnPreResponseExtensions, OnPreResponseInfo, RedirectResponseOptions, - RequestHandler, RequestHandlerWrapper, RequestHandlerContextContainer, RequestHandlerContextProvider, @@ -208,7 +201,6 @@ export type { ResponseHeaders, KibanaResponseFactory, RouteConfig, - IRouter, RouteRegistrar, RouteMethod, RouteConfigOptions, @@ -387,6 +379,7 @@ export type { SavedObjectsImportWarning, SavedObjectsValidationMap, SavedObjectsValidationSpec, + SavedObjectsRequestHandlerContext, } from './saved_objects'; export type { @@ -398,6 +391,7 @@ export type { UiSettingsServiceStart, UserProvidedValues, DeprecationSettings, + UiSettingsRequestHandlerContext, } from './ui_settings'; export type { @@ -421,6 +415,7 @@ export type { GetDeprecationsContext, DeprecationsServiceSetup, DeprecationsClient, + DeprecationsRequestHandlerContext, } from './deprecations'; export type { AppCategory } from '../types'; @@ -473,7 +468,12 @@ export type { AnalyticsServiceStart, } from '@kbn/core-analytics-server'; -/** @public **/ +export type { CoreRequestHandlerContext } from './core_route_handler_context'; + +/** + * Base, abstract type for request handler contexts. + * @public + **/ export interface RequestHandlerContextBase { /** * Await all the specified context parts and return them. @@ -491,7 +491,7 @@ export interface RequestHandlerContextBase { } /** - * Base context passed to a route handler. + * Base context passed to a route handler, containing the `core` context part. * * @public */ @@ -499,43 +499,21 @@ export interface RequestHandlerContext extends RequestHandlerContextBase { core: Promise; } -/** @public */ -export type CustomRequestHandlerContext = RequestHandlerContext & { - [Key in keyof T]: T[Key] extends Promise ? T[Key] : Promise; -}; +/** + * @internal + */ +export interface PrebootRequestHandlerContext extends RequestHandlerContextBase { + core: Promise; +} /** - * The `core` context provided to route handler. + * Mixin allowing plugins to define their own request handler contexts. * - * Provides the following clients and services: - * - {@link SavedObjectsClient | savedObjects.client} - Saved Objects client - * which uses the credentials of the incoming request - * - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing - * all the registered types. - * - {@link IScopedClusterClient | elasticsearch.client} - Elasticsearch - * data client which uses the credentials of the incoming request - * - {@link IUiSettingsClient | uiSettings.client} - uiSettings client - * which uses the credentials of the incoming request * @public */ -export interface CoreRequestHandlerContext { - savedObjects: { - client: SavedObjectsClientContract; - typeRegistry: ISavedObjectTypeRegistry; - getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; - getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter; - getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter; - }; - elasticsearch: { - client: IScopedClusterClient; - }; - uiSettings: { - client: IUiSettingsClient; - }; - deprecations: { - client: DeprecationsClient; - }; -} +export type CustomRequestHandlerContext = RequestHandlerContext & { + [Key in keyof T]: T[Key] extends Promise ? T[Key] : Promise; +}; /** * Context passed to the `setup` method of `preboot` plugins. @@ -547,7 +525,7 @@ export interface CorePreboot { /** {@link ElasticsearchServicePreboot} */ elasticsearch: ElasticsearchServicePreboot; /** {@link HttpServicePreboot} */ - http: HttpServicePreboot; + http: HttpServicePreboot; /** {@link PrebootServicePreboot} */ preboot: PrebootServicePreboot; } @@ -573,7 +551,7 @@ export interface CoreSetup & { /** {@link HttpResources} */ resources: HttpResources; }; @@ -662,3 +640,42 @@ export const config = { appenders: appendersSchema as Type, }, }; + +/** + * Public version of RequestHandler, default-scoped to {@link RequestHandlerContext} + * See [@link RequestHandler} + * @public + */ +type PublicRequestHandler< + P = unknown, + Q = unknown, + B = unknown, + Context extends RequestHandlerContext = RequestHandlerContext, + Method extends RouteMethod = any, + ResponseFactory extends KibanaResponseFactory = KibanaResponseFactory +> = RequestHandler; + +export type { PublicRequestHandler as RequestHandler, RequestHandler as BaseRequestHandler }; + +/** + * Public version of IRouter, default-scoped to {@link RequestHandlerContext} + * See [@link IRouter} + * @public + */ +type PublicRouter = + IRouter; + +export type { PublicRouter as IRouter, IRouter as IBaseRouter }; + +/** + * Public version of RequestHandlerContext, default-scoped to {@link RequestHandlerContext} + * See [@link RequestHandlerContext} + * @public + */ +type PublicHttpServiceSetup = + HttpServiceSetup; + +export type { + PublicHttpServiceSetup as HttpServiceSetup, + HttpServiceSetup as BaseHttpServiceSetup, +}; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 0382d9f56ca1d..f224ff76418ab 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -23,6 +23,7 @@ import type { CoreStart, StartServicesAccessor, CorePreboot, + RequestHandlerContext, } from '.'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -139,7 +140,7 @@ function createCorePrebootMock() { const mock: CorePrebootMockType = { analytics: analyticsServiceMock.createAnalyticsServicePreboot(), elasticsearch: elasticsearchServiceMock.createPreboot(), - http: httpServiceMock.createPrebootContract(), + http: httpServiceMock.createPrebootContract() as CorePrebootMockType['http'], preboot: prebootServiceMock.createPrebootContract(), }; @@ -159,7 +160,7 @@ function createCoreSetupMock({ pluginStartContract?: any; } = {}) { const httpMock: jest.Mocked = { - ...httpServiceMock.createSetupContract(), + ...httpServiceMock.createSetupContract(), resources: httpResourcesMock.createRegistrar(), }; diff --git a/src/core/server/preboot_core_route_handler_context.ts b/src/core/server/preboot_core_route_handler_context.ts index 63378046e8050..ba3cc445f6958 100644 --- a/src/core/server/preboot_core_route_handler_context.ts +++ b/src/core/server/preboot_core_route_handler_context.ts @@ -10,12 +10,34 @@ import { InternalCorePreboot } from './internal_types'; import { IUiSettingsClient } from './ui_settings'; -class PrebootCoreUiSettingsRouteHandlerContext { +/** + * @public + */ +export interface PrebootUiSettingsRequestHandlerContext { + client: IUiSettingsClient; +} + +/** + * Implementation of {@link PrebootUiSettingsRequestHandlerContext} + * @internal + */ +class PrebootCoreUiSettingsRouteHandlerContext implements PrebootUiSettingsRequestHandlerContext { constructor(public readonly client: IUiSettingsClient) {} } -export class PrebootCoreRouteHandlerContext { - readonly uiSettings: PrebootCoreUiSettingsRouteHandlerContext; +/** + * @public + */ +export interface PrebootCoreRequestHandlerContext { + uiSettings: PrebootUiSettingsRequestHandlerContext; +} + +/** + * Implementation of {@link PrebootCoreRequestHandlerContext}. + * @internal + */ +export class PrebootCoreRouteHandlerContext implements PrebootCoreRequestHandlerContext { + readonly uiSettings: PrebootUiSettingsRequestHandlerContext; constructor(private readonly corePreboot: InternalCorePreboot) { this.uiSettings = new PrebootCoreUiSettingsRouteHandlerContext( diff --git a/src/core/server/rendering/bootstrap/register_bootstrap_route.ts b/src/core/server/rendering/bootstrap/register_bootstrap_route.ts index 06f4100a01576..c22f7f56d70f2 100644 --- a/src/core/server/rendering/bootstrap/register_bootstrap_route.ts +++ b/src/core/server/rendering/bootstrap/register_bootstrap_route.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { IRouter } from '../../http'; +import type { InternalRenderingRouter } from '../internal_types'; import type { BootstrapRenderer } from './bootstrap_renderer'; export const registerBootstrapRoute = ({ router, renderer, }: { - router: IRouter; + router: InternalRenderingRouter; renderer: BootstrapRenderer; }) => { router.get( diff --git a/src/core/server/rendering/internal_types.ts b/src/core/server/rendering/internal_types.ts new file mode 100644 index 0000000000000..6ad3fd086f819 --- /dev/null +++ b/src/core/server/rendering/internal_types.ts @@ -0,0 +1,28 @@ +/* + * 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 { RequestHandlerContextBase } from '..'; +import type { IRouter } from '../http'; +import type { UiSettingsRequestHandlerContext } from '../ui_settings'; + +/** + * Request handler context used by core's rendering routes. + * @internal + */ +export interface InternalRenderingRequestHandlerContext extends RequestHandlerContextBase { + core: Promise<{ + uiSettings: UiSettingsRequestHandlerContext; + }>; +} + +/** + * Router bound to the {@link InternalRenderingRequestHandlerContext}. + * Used by core's rendering routes. + * @internal + */ +export type InternalRenderingRouter = IRouter; diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 2ce22b731b5e5..ac6ff9ee2f2b8 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -29,6 +29,7 @@ import { getSettingValue, getStylesheetPaths } from './render_utils'; import type { HttpAuth, KibanaRequest } from '../http'; import { IUiSettingsClient } from '../ui_settings'; import { filterUiPlugins } from './filter_ui_plugins'; +import type { InternalRenderingRequestHandlerContext } from './internal_types'; type RenderOptions = | (RenderingPrebootDeps & { status?: never; elasticsearch?: never }) @@ -42,7 +43,7 @@ export class RenderingService { http, uiPlugins, }: RenderingPrebootDeps): Promise { - http.registerRoutes('', (router) => { + http.registerRoutes('', (router) => { registerBootstrapRoute({ router, renderer: bootstrapRendererFactory({ @@ -66,7 +67,7 @@ export class RenderingService { uiPlugins, }: RenderingSetupDeps): Promise { registerBootstrapRoute({ - router: http.createRouter(''), + router: http.createRouter(''), renderer: bootstrapRendererFactory({ uiPlugins, serverBasePath: http.basePath.serverBasePath, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 73a20c0402bbe..434096193ff9f 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -99,3 +99,5 @@ export type { SavedObjectsValidationMap, SavedObjectsValidationSpec } from './va export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config'; export { SavedObjectTypeRegistry } from './saved_objects_type_registry'; export type { ISavedObjectTypeRegistry } from './saved_objects_type_registry'; +export { CoreSavedObjectsRouteHandlerContext } from './saved_objects_route_handler_context'; +export type { SavedObjectsRequestHandlerContext } from './saved_objects_route_handler_context'; diff --git a/src/core/server/saved_objects/internal_types.ts b/src/core/server/saved_objects/internal_types.ts new file mode 100644 index 0000000000000..6d3851fca120b --- /dev/null +++ b/src/core/server/saved_objects/internal_types.ts @@ -0,0 +1,30 @@ +/* + * 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 { RequestHandlerContextBase } from '..'; +import type { IRouter } from '../http'; +import type { ElasticsearchRequestHandlerContext } from '../elasticsearch'; +import type { SavedObjectsRequestHandlerContext } from './saved_objects_route_handler_context'; + +/** + * Request handler context used by core's savedObjects routes. + * @internal + */ +export interface InternalSavedObjectsRequestHandlerContext extends RequestHandlerContextBase { + core: Promise<{ + savedObjects: SavedObjectsRequestHandlerContext; + elasticsearch: ElasticsearchRequestHandlerContext; + }>; +} + +/** + * Router bound to the {@link InternalSavedObjectsRequestHandlerContext}. + * Used by core's savedObjects routes. + * @internal + */ +export type InternalSavedObjectRouter = IRouter; 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 552cd86e6cb93..4c21ba704fe48 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 @@ -31,6 +31,11 @@ Object { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", @@ -201,6 +206,11 @@ Object { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", @@ -375,6 +385,11 @@ Object { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", @@ -553,6 +568,11 @@ Object { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", @@ -773,6 +793,11 @@ Object { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", @@ -954,6 +979,11 @@ Object { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", diff --git a/src/core/server/saved_objects/migrations/actions/es_errors.test.ts b/src/core/server/saved_objects/migrations/actions/es_errors.test.ts index b34366b7386d2..f0004851b1bf9 100644 --- a/src/core/server/saved_objects/migrations/actions/es_errors.test.ts +++ b/src/core/server/saved_objects/migrations/actions/es_errors.test.ts @@ -9,6 +9,7 @@ import { isClusterShardLimitExceeded, isIncompatibleMappingException, + isIndexNotFoundException, isWriteBlockException, } from './es_errors'; @@ -37,6 +38,9 @@ describe('isWriteBlockError', () => { }) ).toEqual(false); }); + it('returns false undefined', () => { + expect(isWriteBlockException(undefined)).toEqual(false); + }); }); describe('isIncompatibleMappingExceptionError', () => { @@ -57,6 +61,31 @@ describe('isIncompatibleMappingExceptionError', () => { }) ).toEqual(true); }); + it('returns false undefined', () => { + expect(isIncompatibleMappingException(undefined)).toEqual(false); + }); +}); + +describe('isIndexNotFoundException', () => { + it('returns true with index_not_found_exception errors', () => { + expect( + isIndexNotFoundException({ + type: 'index_not_found_exception', + reason: 'idk', + }) + ).toEqual(true); + }); + it('returns false for other errors', () => { + expect( + isIndexNotFoundException({ + type: 'validation_exception', + reason: 'idk', + }) + ).toEqual(false); + }); + it('returns false undefined', () => { + expect(isIndexNotFoundException(undefined)).toEqual(false); + }); }); describe('isClusterShardLimitExceeded', () => { @@ -77,4 +106,7 @@ describe('isClusterShardLimitExceeded', () => { }) ).toEqual(false); }); + it('returns false undefined', () => { + expect(isClusterShardLimitExceeded(undefined)).toEqual(false); + }); }); diff --git a/src/core/server/saved_objects/migrations/actions/es_errors.ts b/src/core/server/saved_objects/migrations/actions/es_errors.ts index 9f571d38ffd85..2bf432de032a6 100644 --- a/src/core/server/saved_objects/migrations/actions/es_errors.ts +++ b/src/core/server/saved_objects/migrations/actions/es_errors.ts @@ -7,25 +7,28 @@ */ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -export const isWriteBlockException = ({ type, reason }: estypes.ErrorCause): boolean => { +export const isWriteBlockException = (errorCause?: estypes.ErrorCause): boolean => { return ( - type === 'cluster_block_exception' && - reason.match(/index \[.+] blocked by: \[FORBIDDEN\/8\/.+ \(api\)\]/) !== null + errorCause?.type === 'cluster_block_exception' && + errorCause?.reason.match(/index \[.+] blocked by: \[FORBIDDEN\/8\/.+ \(api\)\]/) !== null ); }; -export const isIncompatibleMappingException = ({ type }: estypes.ErrorCause): boolean => { - return type === 'strict_dynamic_mapping_exception' || type === 'mapper_parsing_exception'; +export const isIncompatibleMappingException = (errorCause?: estypes.ErrorCause): boolean => { + return ( + errorCause?.type === 'strict_dynamic_mapping_exception' || + errorCause?.type === 'mapper_parsing_exception' + ); }; -export const isIndexNotFoundException = ({ type }: estypes.ErrorCause): boolean => { - return type === 'index_not_found_exception'; +export const isIndexNotFoundException = (errorCause?: estypes.ErrorCause): boolean => { + return errorCause?.type === 'index_not_found_exception'; }; -export const isClusterShardLimitExceeded = ({ type, reason }: estypes.ErrorCause): boolean => { +export const isClusterShardLimitExceeded = (errorCause?: estypes.ErrorCause): boolean => { return ( - type === 'validation_exception' && - reason.match( + errorCause?.type === 'validation_exception' && + errorCause?.reason.match( /this action would add .* shards, but this cluster currently has .* maximum normal shards open/ ) !== null ); 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 d80dcb23b7189..480395241c278 100644 --- a/src/core/server/saved_objects/migrations/core/unused_types.ts +++ b/src/core/server/saved_objects/migrations/core/unused_types.ts @@ -35,6 +35,8 @@ export const REMOVED_TYPES: string[] = [ 'timelion-sheet', // Removed in 8.3 https://github.com/elastic/kibana/issues/127745 'ui-counter', + // Deprecated, no longer used since 7.13 https://github.com/elastic/kibana/pull/94923/files + 'application_usage_transactional', ].sort(); // When migrating from the outdated index we use a read query which excludes diff --git a/src/core/server/saved_objects/migrations/initial_state.test.ts b/src/core/server/saved_objects/migrations/initial_state.test.ts index 41f2530cb7f87..32e3b9f50f4a3 100644 --- a/src/core/server/saved_objects/migrations/initial_state.test.ts +++ b/src/core/server/saved_objects/migrations/initial_state.test.ts @@ -65,6 +65,11 @@ describe('createInitialState', () => { "type": "apm-services-telemetry", }, }, + Object { + "term": Object { + "type": "application_usage_transactional", + }, + }, Object { "term": Object { "type": "background-session", diff --git a/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts index 6ab41cb5a991c..66c7d2e81b846 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts @@ -24,6 +24,9 @@ async function removeLogFile() { await unlink(logFilePath).catch(() => void 0); } +/** Number of SO documents dropped during the migration because they belong to an unused type */ +const UNUSED_SO_COUNT = 4; + describe('migration from 7.7.2-xpack with 100k objects', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; @@ -122,6 +125,8 @@ describe('migration from 7.7.2-xpack with 100k objects', () => { // Use a >= comparison since once Kibana has started it might create new // documents like telemetry tasks - expect(migratedIndexResponse.count).toBeGreaterThanOrEqual(oldIndexResponse.count); + expect(migratedIndexResponse.count).toBeGreaterThanOrEqual( + oldIndexResponse.count - UNUSED_SO_COUNT + ); }); }); diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index b72cd6715fa10..6a671c830ca69 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerBulkCreateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerBulkCreateRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.post( { path: '/_bulk_create', diff --git a/src/core/server/saved_objects/routes/bulk_get.ts b/src/core/server/saved_objects/routes/bulk_get.ts index 87b6a604ac6c4..ba485c832ca65 100644 --- a/src/core/server/saved_objects/routes/bulk_get.ts +++ b/src/core/server/saved_objects/routes/bulk_get.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerBulkGetRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerBulkGetRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.post( { path: '/_bulk_get', diff --git a/src/core/server/saved_objects/routes/bulk_resolve.ts b/src/core/server/saved_objects/routes/bulk_resolve.ts index 5754ec180541c..e689e243ebc95 100644 --- a/src/core/server/saved_objects/routes/bulk_resolve.ts +++ b/src/core/server/saved_objects/routes/bulk_resolve.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerBulkResolveRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerBulkResolveRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.post( { path: '/_bulk_resolve', diff --git a/src/core/server/saved_objects/routes/bulk_update.ts b/src/core/server/saved_objects/routes/bulk_update.ts index 961b8349d1745..2edfa23ed8786 100644 --- a/src/core/server/saved_objects/routes/bulk_update.ts +++ b/src/core/server/saved_objects/routes/bulk_update.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerBulkUpdateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerBulkUpdateRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.put( { path: '/_bulk_update', diff --git a/src/core/server/saved_objects/routes/create.ts b/src/core/server/saved_objects/routes/create.ts index eb3938db0a9c7..282cbafc0b2f5 100644 --- a/src/core/server/saved_objects/routes/create.ts +++ b/src/core/server/saved_objects/routes/create.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerCreateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerCreateRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.post( { path: '/{type}/{id?}', diff --git a/src/core/server/saved_objects/routes/delete.ts b/src/core/server/saved_objects/routes/delete.ts index 5c239a55a6923..7410a237d39f7 100644 --- a/src/core/server/saved_objects/routes/delete.ts +++ b/src/core/server/saved_objects/routes/delete.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerDeleteRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerDeleteRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.delete( { path: '/{type}/{id}', diff --git a/src/core/server/saved_objects/routes/deprecations/delete_unknown_types.ts b/src/core/server/saved_objects/routes/deprecations/delete_unknown_types.ts index a30da9723b916..5d36994928363 100644 --- a/src/core/server/saved_objects/routes/deprecations/delete_unknown_types.ts +++ b/src/core/server/saved_objects/routes/deprecations/delete_unknown_types.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { IRouter } from '../../../http'; import { catchAndReturnBoomErrors } from '../utils'; +import type { InternalSavedObjectRouter } from '../../internal_types'; import { deleteUnknownTypeObjects } from '../../deprecations'; interface RouteDependencies { @@ -16,7 +16,7 @@ interface RouteDependencies { } export const registerDeleteUnknownTypesRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, { kibanaIndex, kibanaVersion }: RouteDependencies ) => { router.post( diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index 16547970369c2..e011c71575080 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; import { createPromiseFromStreams, createMapStream, createConcatStream } from '@kbn/utils'; -import { IRouter, KibanaRequest } from '../../http'; +import { KibanaRequest } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { @@ -18,6 +18,7 @@ import { SavedObjectsExportByObjectOptions, SavedObjectsExportError, } from '../export'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { validateTypes, validateObjects, catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { @@ -129,7 +130,7 @@ const validateOptions = ( }; export const registerExportRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, { config, coreUsageData }: RouteDependencies ) => { const { maxImportExportSize } = config; diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts index 01ac9ae9025f4..7ebfef5be7c39 100644 --- a/src/core/server/saved_objects/routes/find.ts +++ b/src/core/server/saved_objects/routes/find.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerFindRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerFindRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { const referenceSchema = schema.object({ type: schema.string(), id: schema.string(), diff --git a/src/core/server/saved_objects/routes/get.ts b/src/core/server/saved_objects/routes/get.ts index 2ea9a13bbce64..e7a2a779c4037 100644 --- a/src/core/server/saved_objects/routes/get.ts +++ b/src/core/server/saved_objects/routes/get.ts @@ -7,15 +7,18 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerGetRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerGetRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.get( { path: '/{type}/{id}', diff --git a/src/core/server/saved_objects/routes/import.ts b/src/core/server/saved_objects/routes/import.ts index 545d01b454741..0c56acf9e2d68 100644 --- a/src/core/server/saved_objects/routes/import.ts +++ b/src/core/server/saved_objects/routes/import.ts @@ -9,10 +9,10 @@ import { Readable } from 'stream'; import { extname } from 'path'; import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { SavedObjectsImportError } from '../import'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; interface RouteDependencies { @@ -27,7 +27,7 @@ interface FileStream extends Readable { } export const registerImportRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, { config, coreUsageData }: RouteDependencies ) => { const { maxImportPayloadBytes } = config; diff --git a/src/core/server/saved_objects/routes/index.ts b/src/core/server/saved_objects/routes/index.ts index f76f802928190..34083e0d6ddf8 100644 --- a/src/core/server/saved_objects/routes/index.ts +++ b/src/core/server/saved_objects/routes/index.ts @@ -11,6 +11,7 @@ import { InternalHttpServiceSetup } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { IKibanaMigrator } from '../migrations'; +import type { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; import { registerGetRoute } from './get'; import { registerResolveRoute } from './resolve'; import { registerCreateRoute } from './create'; @@ -46,7 +47,8 @@ export function registerRoutes({ kibanaVersion: string; kibanaIndex: string; }) { - const router = http.createRouter('/api/saved_objects/'); + const router = + http.createRouter('/api/saved_objects/'); registerGetRoute(router, { coreUsageData }); registerResolveRoute(router, { coreUsageData }); @@ -62,7 +64,7 @@ export function registerRoutes({ registerImportRoute(router, { config, coreUsageData }); registerResolveImportErrorsRoute(router, { config, coreUsageData }); - const legacyRouter = http.createRouter(''); + const legacyRouter = http.createRouter(''); registerLegacyImportRoute(legacyRouter, { maxImportPayloadBytes: config.maxImportPayloadBytes, coreUsageData, @@ -70,7 +72,9 @@ export function registerRoutes({ }); registerLegacyExportRoute(legacyRouter, { kibanaVersion, coreUsageData, logger }); - const internalRouter = http.createRouter('/internal/saved_objects/'); + const internalRouter = http.createRouter( + '/internal/saved_objects/' + ); registerMigrateRoute(internalRouter, migratorPromise); registerDeleteUnknownTypesRoute(internalRouter, { kibanaIndex, kibanaVersion }); diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts index eeec6bd6b0be0..c34ea099d0ec8 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -28,7 +29,8 @@ describe('POST /api/saved_objects/_bulk_create', () => { savedObjectsClient = handlerContext.savedObjects.client; savedObjectsClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsBulkCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts index f389307d45c41..a82a5351e9949 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -30,7 +31,8 @@ describe('POST /api/saved_objects/_bulk_get', () => { savedObjectsClient.bulkGet.mockResolvedValue({ saved_objects: [], }); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsBulkGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_resolve.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_resolve.test.ts index bb5067a13ba3e..90f06368468be 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_resolve.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_resolve.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -30,7 +31,8 @@ describe('POST /api/saved_objects/_bulk_resolve', () => { savedObjectsClient.bulkResolve.mockResolvedValue({ resolved_objects: [], }); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsBulkResolve.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts index 8c55686b2fd0f..d92041bce3c72 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -27,7 +28,8 @@ describe('PUT /api/saved_objects/_bulk_update', () => { ({ server, httpSetup, handlerContext } = await setupServer()); savedObjectsClient = handlerContext.savedObjects.client; - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsBulkUpdate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/create.test.ts b/src/core/server/saved_objects/routes/integration_tests/create.test.ts index 9d65843271cc0..e4f566c769234 100644 --- a/src/core/server/saved_objects/routes/integration_tests/create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/create.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -37,7 +38,8 @@ describe('POST /api/saved_objects/{type}', () => { savedObjectsClient = handlerContext.savedObjects.client; savedObjectsClient.create.mockImplementation(() => Promise.resolve(clientResponse)); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts index 84a2db60b587a..849b99b49fbc0 100644 --- a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -28,7 +29,8 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => { savedObjectsClient = handlerContext.savedObjects.getClient(); handlerContext.savedObjects.getClient = jest.fn().mockImplementation(() => savedObjectsClient); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsDelete.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/delete_unknown_types.test.ts b/src/core/server/saved_objects/routes/integration_tests/delete_unknown_types.test.ts index 60d916744babc..87376de206810 100644 --- a/src/core/server/saved_objects/routes/integration_tests/delete_unknown_types.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/delete_unknown_types.test.ts @@ -12,6 +12,7 @@ import { elasticsearchServiceMock } from '../../../elasticsearch/elasticsearch_s import { typeRegistryMock } from '../../saved_objects_type_registry.mock'; import { setupServer } from '../test_utils'; import { SavedObjectsType } from '../../..'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -37,7 +38,9 @@ describe('POST /internal/saved_objects/deprecations/_delete_unknown_types', () = handlerContext.elasticsearch.client.asCurrentUser = elasticsearchClient.asCurrentUser; handlerContext.elasticsearch.client.asInternalUser = elasticsearchClient.asInternalUser; - const router = httpSetup.createRouter('/internal/saved_objects/'); + const router = httpSetup.createRouter( + '/internal/saved_objects/' + ); registerDeleteUnknownTypesRoute(router, { kibanaVersion, kibanaIndex, diff --git a/src/core/server/saved_objects/routes/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/integration_tests/export.test.ts index 1227d2636c555..9bdd6f39abbd0 100644 --- a/src/core/server/saved_objects/routes/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/export.test.ts @@ -19,6 +19,7 @@ import { savedObjectsExporterMock } from '../../export/saved_objects_exporter.mo import { SavedObjectConfig } from '../../saved_objects_config'; import { registerExportRoute } from '../export'; import { setupServer, createExportableType } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; const allowedTypes = ['index-pattern', 'search']; @@ -41,7 +42,8 @@ describe('POST /api/saved_objects/_export', () => { ); exporter = handlerContext.savedObjects.getExporter(); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); handlerContext.savedObjects.getExporter = jest .fn() .mockImplementation(() => exporter as ReturnType); diff --git a/src/core/server/saved_objects/routes/integration_tests/find.test.ts b/src/core/server/saved_objects/routes/integration_tests/find.test.ts index c18044f8973b8..8758666bc487d 100644 --- a/src/core/server/saved_objects/routes/integration_tests/find.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/find.test.ts @@ -15,6 +15,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -38,7 +39,8 @@ describe('GET /api/saved_objects/_find', () => { savedObjectsClient.find.mockResolvedValue(clientResponse); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsFind.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/get.test.ts b/src/core/server/saved_objects/routes/integration_tests/get.test.ts index 98d9d9170e5eb..8b34c9f2ef958 100644 --- a/src/core/server/saved_objects/routes/integration_tests/get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/get.test.ts @@ -17,6 +17,7 @@ import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_da import { HttpService, InternalHttpServiceSetup } from '../../../http'; import { createHttpServer, createCoreContext } from '../../../http/test_utils'; import { contextServiceMock, coreMock } from '../../../mocks'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; const coreId = Symbol('core'); @@ -41,11 +42,16 @@ describe('GET /api/saved_objects/{type}/{id}', () => { handlerContext = coreMock.createRequestHandlerContext(); savedObjectsClient = handlerContext.savedObjects.client; - httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { - return handlerContext; - }); + httpSetup.registerRouteHandlerContext( + coreId, + 'core', + (ctx, req, res) => { + return handlerContext; + } + ); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/integration_tests/import.test.ts index 2f6f206366766..9e6e52977b2e1 100644 --- a/src/core/server/saved_objects/routes/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/import.test.ts @@ -17,6 +17,7 @@ import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_da import { SavedObjectConfig } from '../../saved_objects_config'; import { setupServer, createExportableType } from '../test_utils'; import { SavedObjectsErrorHelpers, SavedObjectsImporter } from '../..'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -69,7 +70,9 @@ describe(`POST ${URL}`, () => { .fn() .mockImplementation(() => importer as jest.Mocked); - const router = httpSetup.createRouter('/internal/saved_objects/'); + const router = httpSetup.createRouter( + '/internal/saved_objects/' + ); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsImport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts index f551abda188ef..78397ca00cb60 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve.test.ts @@ -17,6 +17,7 @@ import { executionContextServiceMock } from '@kbn/core-execution-context-server- import { HttpService, InternalHttpServiceSetup } from '../../../http'; import { createHttpServer, createCoreContext } from '../../../http/test_utils'; import { contextServiceMock, coreMock } from '../../../mocks'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; const coreId = Symbol('core'); @@ -41,11 +42,16 @@ describe('GET /api/saved_objects/resolve/{type}/{id}', () => { handlerContext = coreMock.createRequestHandlerContext(); savedObjectsClient = handlerContext.savedObjects.client; - httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { - return handlerContext; - }); + httpSetup.registerRouteHandlerContext( + coreId, + 'core', + (ctx, req, res) => { + return handlerContext; + } + ); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsResolve.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts index dfc69edaff420..101cf05032618 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts @@ -17,6 +17,7 @@ import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_da import { setupServer, createExportableType } from '../test_utils'; import { SavedObjectConfig } from '../../saved_objects_config'; import { SavedObjectsImporter } from '../..'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -76,7 +77,8 @@ describe(`POST ${URL}`, () => { .fn() .mockImplementation(() => importer as jest.Mocked); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsResolveImportErrors.mockRejectedValue( new Error('Oh no!') // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail diff --git a/src/core/server/saved_objects/routes/integration_tests/update.test.ts b/src/core/server/saved_objects/routes/integration_tests/update.test.ts index 78e7b9c1e93d5..1a41d94b0febe 100644 --- a/src/core/server/saved_objects/routes/integration_tests/update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/update.test.ts @@ -13,6 +13,7 @@ import { CoreUsageStatsClient } from '../../../core_usage_data'; import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../internal_types'; type SetupServerReturn = Awaited>; @@ -38,7 +39,8 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { savedObjectsClient = handlerContext.savedObjects.client; savedObjectsClient.update.mockResolvedValue(clientResponse); - const router = httpSetup.createRouter('/api/saved_objects/'); + const router = + httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsUpdate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); diff --git a/src/core/server/saved_objects/routes/legacy_import_export/export.ts b/src/core/server/saved_objects/routes/legacy_import_export/export.ts index 7141d74b71904..9073090de8fe7 100644 --- a/src/core/server/saved_objects/routes/legacy_import_export/export.ts +++ b/src/core/server/saved_objects/routes/legacy_import_export/export.ts @@ -8,12 +8,13 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; +import type { Logger } from '@kbn/logging'; import { InternalCoreUsageDataSetup } from '../../../core_usage_data'; -import { IRouter, Logger } from '../../..'; +import type { InternalSavedObjectRouter } from '../../internal_types'; import { exportDashboards } from './lib'; export const registerLegacyExportRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, { kibanaVersion, coreUsageData, diff --git a/src/core/server/saved_objects/routes/legacy_import_export/import.ts b/src/core/server/saved_objects/routes/legacy_import_export/import.ts index d98c14f9b620d..d4a13b2973964 100644 --- a/src/core/server/saved_objects/routes/legacy_import_export/import.ts +++ b/src/core/server/saved_objects/routes/legacy_import_export/import.ts @@ -7,12 +7,14 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter, Logger, SavedObject } from '../../..'; +import type { Logger } from '@kbn/logging'; +import type { SavedObject } from '../../..'; import { InternalCoreUsageDataSetup } from '../../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../../internal_types'; import { importDashboards } from './lib'; export const registerLegacyImportRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, { maxImportPayloadBytes, coreUsageData, diff --git a/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/export.test.ts index 6ae4a74aee014..7d0a8607fb6ed 100644 --- a/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/export.test.ts @@ -38,6 +38,7 @@ import { coreUsageDataServiceMock } from '../../../../core_usage_data/core_usage import { registerLegacyExportRoute } from '../export'; import { setupServer } from '../../test_utils'; import { loggerMock } from '@kbn/logging-mocks'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../../internal_types'; type SetupServerReturn = Awaited>; let coreUsageStatsClient: jest.Mocked; @@ -49,7 +50,7 @@ describe('POST /api/dashboards/export', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer()); - const router = httpSetup.createRouter(''); + const router = httpSetup.createRouter(''); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementLegacyDashboardsExport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail diff --git a/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/import.test.ts index 13d5638440547..37f82aa47ece5 100644 --- a/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/legacy_import_export/integration_tests/import.test.ts @@ -38,6 +38,7 @@ import { coreUsageDataServiceMock } from '../../../../core_usage_data/core_usage import { registerLegacyImportRoute } from '../import'; import { setupServer } from '../../test_utils'; import { loggerMock } from '@kbn/logging-mocks'; +import type { InternalSavedObjectsRequestHandlerContext } from '../../../internal_types'; type SetupServerReturn = Awaited>; let coreUsageStatsClient: jest.Mocked; @@ -49,7 +50,7 @@ describe('POST /api/dashboards/import', () => { beforeEach(async () => { ({ server, httpSetup } = await setupServer()); - const router = httpSetup.createRouter(''); + const router = httpSetup.createRouter(''); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementLegacyDashboardsImport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail diff --git a/src/core/server/saved_objects/routes/migrate.ts b/src/core/server/saved_objects/routes/migrate.ts index 404074124c92b..05579855cbfc3 100644 --- a/src/core/server/saved_objects/routes/migrate.ts +++ b/src/core/server/saved_objects/routes/migrate.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { IRouter } from '../../http'; import { IKibanaMigrator } from '../migrations'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; export const registerMigrateRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, migratorPromise: Promise ) => { router.post( diff --git a/src/core/server/saved_objects/routes/resolve.ts b/src/core/server/saved_objects/routes/resolve.ts index ae09f6526baa3..b0a7153e8d226 100644 --- a/src/core/server/saved_objects/routes/resolve.ts +++ b/src/core/server/saved_objects/routes/resolve.ts @@ -7,14 +7,17 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; +import type { InternalSavedObjectRouter } from '../internal_types'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerResolveRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerResolveRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.get( { path: '/resolve/{type}/{id}', diff --git a/src/core/server/saved_objects/routes/resolve_import_errors.ts b/src/core/server/saved_objects/routes/resolve_import_errors.ts index bf536e906d7da..4bedec1715a4f 100644 --- a/src/core/server/saved_objects/routes/resolve_import_errors.ts +++ b/src/core/server/saved_objects/routes/resolve_import_errors.ts @@ -10,11 +10,12 @@ import { extname } from 'path'; import { Readable } from 'stream'; import { schema } from '@kbn/config-schema'; import { chain } from 'lodash'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { SavedObjectsImportError } from '../import'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; + interface RouteDependencies { config: SavedObjectConfig; coreUsageData: InternalCoreUsageDataSetup; @@ -27,7 +28,7 @@ interface FileStream extends Readable { } export const registerResolveImportErrorsRoute = ( - router: IRouter, + router: InternalSavedObjectRouter, { config, coreUsageData }: RouteDependencies ) => { const { maxImportPayloadBytes } = config; diff --git a/src/core/server/saved_objects/routes/test_utils.ts b/src/core/server/saved_objects/routes/test_utils.ts index 0d5e7588ad6ce..5783ecb9e5e27 100644 --- a/src/core/server/saved_objects/routes/test_utils.ts +++ b/src/core/server/saved_objects/routes/test_utils.ts @@ -11,6 +11,7 @@ import { ContextService } from '../../context'; import { createHttpServer, createCoreContext } from '../../http/test_utils'; import { contextServiceMock, coreMock } from '../../mocks'; import { SavedObjectsType } from '../types'; +import { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; const defaultCoreId = Symbol('core'); @@ -26,9 +27,13 @@ export const setupServer = async (coreId: symbol = defaultCoreId) => { }); const handlerContext = coreMock.createRequestHandlerContext(); - httpSetup.registerRouteHandlerContext(coreId, 'core', async (ctx, req, res) => { - return handlerContext; - }); + httpSetup.registerRouteHandlerContext( + coreId, + 'core', + (ctx, req, res) => { + return handlerContext; + } + ); return { server, diff --git a/src/core/server/saved_objects/routes/update.ts b/src/core/server/saved_objects/routes/update.ts index 5383ab76a1e4d..3d68c6a698a04 100644 --- a/src/core/server/saved_objects/routes/update.ts +++ b/src/core/server/saved_objects/routes/update.ts @@ -7,16 +7,19 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { InternalCoreUsageDataSetup } from '../../core_usage_data'; import type { SavedObjectsUpdateOptions } from '../service/saved_objects_client'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; } -export const registerUpdateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { +export const registerUpdateRoute = ( + router: InternalSavedObjectRouter, + { coreUsageData }: RouteDependencies +) => { router.put( { path: '/{type}/{id}', diff --git a/src/core/server/saved_objects/saved_objects_route_handler_context.ts b/src/core/server/saved_objects/saved_objects_route_handler_context.ts new file mode 100644 index 0000000000000..bda45de389c98 --- /dev/null +++ b/src/core/server/saved_objects/saved_objects_route_handler_context.ts @@ -0,0 +1,68 @@ +/* + * 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 { KibanaRequest } from '../http'; +import type { InternalSavedObjectsServiceStart } from './saved_objects_service'; +import type { ISavedObjectTypeRegistry } from './saved_objects_type_registry'; +import type { SavedObjectsClientContract } from './types'; +import type { SavedObjectsClientProviderOptions } from './service'; +import type { ISavedObjectsExporter } from './export'; +import type { ISavedObjectsImporter } from './import'; + +/** + * Core's `savedObjects` request handler context. + * @public + */ +export interface SavedObjectsRequestHandlerContext { + client: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; + getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; + getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter; + getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter; +} + +/** + * The {@link SavedObjectsRequestHandlerContext} implementation. + * @internal + */ +export class CoreSavedObjectsRouteHandlerContext implements SavedObjectsRequestHandlerContext { + constructor( + private readonly savedObjectsStart: InternalSavedObjectsServiceStart, + private readonly request: KibanaRequest + ) {} + + #scopedSavedObjectsClient?: SavedObjectsClientContract; + #typeRegistry?: ISavedObjectTypeRegistry; + + public get client() { + if (this.#scopedSavedObjectsClient == null) { + this.#scopedSavedObjectsClient = this.savedObjectsStart.getScopedClient(this.request); + } + return this.#scopedSavedObjectsClient; + } + + public get typeRegistry() { + if (this.#typeRegistry == null) { + this.#typeRegistry = this.savedObjectsStart.getTypeRegistry(); + } + return this.#typeRegistry; + } + + public getClient = (options?: SavedObjectsClientProviderOptions) => { + if (!options) return this.client; + return this.savedObjectsStart.getScopedClient(this.request, options); + }; + + public getExporter = (client: SavedObjectsClientContract) => { + return this.savedObjectsStart.createExporter(client); + }; + + public getImporter = (client: SavedObjectsClientContract) => { + return this.savedObjectsStart.createImporter(client); + }; +} diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 23586396bf62b..3f877d31eec3a 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -57,6 +57,7 @@ import { CoreRouteHandlerContext } from './core_route_handler_context'; import { PrebootCoreRouteHandlerContext } from './preboot_core_route_handler_context'; import { PrebootService } from './preboot'; import { DiscoveredPlugins } from './plugins'; +import type { RequestHandlerContext, PrebootRequestHandlerContext } from '.'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -208,9 +209,13 @@ export class Server { await this.plugins.preboot(corePreboot); - httpPreboot.registerRouteHandlerContext(coreId, 'core', (() => { - return new PrebootCoreRouteHandlerContext(corePreboot); - }) as any); + httpPreboot.registerRouteHandlerContext( + coreId, + 'core', + () => { + return new PrebootCoreRouteHandlerContext(corePreboot); + } + ); this.coreApp.preboot(corePreboot, uiPlugins); @@ -413,9 +418,13 @@ export class Server { } private registerCoreContext(coreSetup: InternalCoreSetup) { - coreSetup.http.registerRouteHandlerContext(coreId, 'core', async (context, req, res) => { - return new CoreRouteHandlerContext(this.coreStart!, req); - }); + coreSetup.http.registerRouteHandlerContext( + coreId, + 'core', + (context, req) => { + return new CoreRouteHandlerContext(this.coreStart!, req); + } + ); } public setupCoreConfig() { diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index d83d9c4358621..ee8cd7181fe8d 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -10,6 +10,8 @@ export type { UiSettingsClient, UiSettingsServiceOptions } from './ui_settings_c export { config } from './ui_settings_config'; export { UiSettingsService } from './ui_settings_service'; +export { CoreUiSettingsRouteHandlerContext } from './ui_settings_route_handler_context'; +export type { UiSettingsRequestHandlerContext } from './ui_settings_route_handler_context'; export type { UiSettingsServiceSetup, diff --git a/src/core/server/ui_settings/internal_types.ts b/src/core/server/ui_settings/internal_types.ts new file mode 100644 index 0000000000000..4292cca94ffbd --- /dev/null +++ b/src/core/server/ui_settings/internal_types.ts @@ -0,0 +1,28 @@ +/* + * 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 { RequestHandlerContextBase } from '..'; +import type { IRouter } from '../http'; +import type { UiSettingsRequestHandlerContext } from './ui_settings_route_handler_context'; + +/** + * Request handler context used by core's uiSetting routes. + * @internal + */ +export interface InternalUiSettingsRequestHandlerContext extends RequestHandlerContextBase { + core: Promise<{ + uiSettings: UiSettingsRequestHandlerContext; + }>; +} + +/** + * Router bound to the {@link InternalUiSettingsRequestHandlerContext}. + * Used by core's uiSetting routes. + * @internal + */ +export type InternalUiSettingsRouter = IRouter; diff --git a/src/core/server/ui_settings/routes/delete.ts b/src/core/server/ui_settings/routes/delete.ts index 87c6edf386428..705a5319c1cf5 100644 --- a/src/core/server/ui_settings/routes/delete.ts +++ b/src/core/server/ui_settings/routes/delete.ts @@ -8,8 +8,8 @@ import { schema } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import type { InternalUiSettingsRouter } from '../internal_types'; import { CannotOverrideError } from '../ui_settings_errors'; const validate = { @@ -18,7 +18,7 @@ const validate = { }), }; -export function registerDeleteRoute(router: IRouter) { +export function registerDeleteRoute(router: InternalUiSettingsRouter) { router.delete( { path: '/api/kibana/settings/{key}', validate }, async (context, request, response) => { diff --git a/src/core/server/ui_settings/routes/get.ts b/src/core/server/ui_settings/routes/get.ts index 0929330cf0238..c940c2e1fe71e 100644 --- a/src/core/server/ui_settings/routes/get.ts +++ b/src/core/server/ui_settings/routes/get.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { IRouter } from '../../http'; +import type { InternalUiSettingsRouter } from '../internal_types'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; -export function registerGetRoute(router: IRouter) { +export function registerGetRoute(router: InternalUiSettingsRouter) { router.get( { path: '/api/kibana/settings', validate: false }, async (context, request, response) => { diff --git a/src/core/server/ui_settings/routes/index.ts b/src/core/server/ui_settings/routes/index.ts index 0cf7233b8af19..22ca2ae38cde5 100644 --- a/src/core/server/ui_settings/routes/index.ts +++ b/src/core/server/ui_settings/routes/index.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import { IRouter } from '../..'; - +import type { InternalUiSettingsRouter } from '../internal_types'; import { registerDeleteRoute } from './delete'; import { registerGetRoute } from './get'; import { registerSetManyRoute } from './set_many'; import { registerSetRoute } from './set'; -export function registerRoutes(router: IRouter) { +export function registerRoutes(router: InternalUiSettingsRouter) { registerGetRoute(router); registerDeleteRoute(router); registerSetRoute(router); diff --git a/src/core/server/ui_settings/routes/set.ts b/src/core/server/ui_settings/routes/set.ts index 91518fb6f3476..af62fda0144b6 100644 --- a/src/core/server/ui_settings/routes/set.ts +++ b/src/core/server/ui_settings/routes/set.ts @@ -8,8 +8,8 @@ import { schema, ValidationError } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import type { InternalUiSettingsRouter } from '../internal_types'; import { CannotOverrideError } from '../ui_settings_errors'; const validate = { @@ -21,7 +21,7 @@ const validate = { }), }; -export function registerSetRoute(router: IRouter) { +export function registerSetRoute(router: InternalUiSettingsRouter) { router.post( { path: '/api/kibana/settings/{key}', validate }, async (context, request, response) => { diff --git a/src/core/server/ui_settings/routes/set_many.ts b/src/core/server/ui_settings/routes/set_many.ts index f4f3f509bf920..fe0ee1a0a721f 100644 --- a/src/core/server/ui_settings/routes/set_many.ts +++ b/src/core/server/ui_settings/routes/set_many.ts @@ -8,8 +8,8 @@ import { schema, ValidationError } from '@kbn/config-schema'; -import { IRouter } from '../../http'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import type { InternalUiSettingsRouter } from '../internal_types'; import { CannotOverrideError } from '../ui_settings_errors'; const validate = { @@ -18,7 +18,7 @@ const validate = { }), }; -export function registerSetManyRoute(router: IRouter) { +export function registerSetManyRoute(router: InternalUiSettingsRouter) { router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => { try { const uiSettingsClient = (await context.core).uiSettings.client; diff --git a/src/core/server/ui_settings/ui_settings_route_handler_context.ts b/src/core/server/ui_settings/ui_settings_route_handler_context.ts new file mode 100644 index 0000000000000..a975f0faad6ef --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_route_handler_context.ts @@ -0,0 +1,40 @@ +/* + * 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 { CoreSavedObjectsRouteHandlerContext } from '../saved_objects'; +import type { IUiSettingsClient, InternalUiSettingsServiceStart } from './types'; + +/** + * Core's `uiSettings` request handler context. + * @public + */ +export interface UiSettingsRequestHandlerContext { + client: IUiSettingsClient; +} + +/** + * The {@link UiSettingsRequestHandlerContext} implementation. + * @internal + */ +export class CoreUiSettingsRouteHandlerContext implements UiSettingsRequestHandlerContext { + #client?: IUiSettingsClient; + + constructor( + private readonly uiSettingsStart: InternalUiSettingsServiceStart, + private readonly savedObjectsRouterHandlerContext: CoreSavedObjectsRouteHandlerContext + ) {} + + public get client() { + if (this.#client == null) { + this.#client = this.uiSettingsStart.asScopedToClient( + this.savedObjectsRouterHandlerContext.client + ); + } + return this.#client; + } +} diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index d303060d55595..342514660e8d1 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -26,6 +26,7 @@ import { uiSettingsType } from './saved_objects'; import { registerRoutes } from './routes'; import { getCoreSettings } from './settings'; import { UiSettingsDefaultsClient } from './ui_settings_defaults_client'; +import type { InternalUiSettingsRequestHandlerContext } from './internal_types'; export interface SetupDeps { http: InternalHttpServiceSetup; @@ -70,7 +71,7 @@ export class UiSettingsService this.log.debug('Setting up ui settings service'); savedObjects.registerType(uiSettingsType); - registerRoutes(http.createRouter('')); + registerRoutes(http.createRouter('')); const config = await firstValueFrom(this.config$); this.overrides = config.overrides; diff --git a/src/dev/bazel/pkg_npm_types.bzl b/src/dev/bazel/pkg_npm_types.bzl index 03ec4209f6f6c..8b8de4a68912a 100644 --- a/src/dev/bazel/pkg_npm_types.bzl +++ b/src/dev/bazel/pkg_npm_types.bzl @@ -19,6 +19,7 @@ def _collect_inputs_deps_and_transitive_types_deps(ctx): """Returns an array with all transitively referenced files on deps in the pos 0 and all types deps in pos 1""" deps_files_depsets = [] transitive_types_deps = [] + for dep in ctx.attr.deps: # Collect whatever is in the "data" deps_files_depsets.append(dep.data_runfiles.files) @@ -29,6 +30,7 @@ def _collect_inputs_deps_and_transitive_types_deps(ctx): # Collect transitive type deps to propagate in the provider if DeclarationInfo in dep: transitive_types_deps.append(dep) + deps_files_depsets.append(dep[DeclarationInfo].transitive_declarations) deps_files = depset(transitive = deps_files_depsets).to_list() return [deps_files, transitive_types_deps] @@ -97,6 +99,9 @@ def _pkg_npm_types_impl(ctx): mnemonic = "AssembleNpmTypesPackage", progress_message = "Assembling npm types package %s" % package_dir.short_path, executable = "_packager", + env = { + "FORCE_COLOR": "1" + } ) # this is a tree artifact, so correctly build the return @@ -109,6 +114,11 @@ def _pkg_npm_types_impl(ctx): ), declaration_info( declarations = depset([package_dir]), + # this includes all the dependencies and transitive dependnecies of the ts_project, but the + # actual dependencies of the type summarizer output are just a subset of these. We don't currently + # know any way to pass the list of dependecies from the type summarizer back to bazel, so we use + # this larger-than-necessary list for accuracy, but we will likely need to figure this out once + # we have a much larger dependency graph. deps = transitive_types_deps, ), LinkablePackageInfo( @@ -143,7 +153,7 @@ pkg_npm_types = rule( doc = "Target that executes the npm types package assembler binary", executable = True, cfg = "target", - default = Label("//packages/kbn-type-summarizer:bazel-cli"), + default = Label("//packages/kbn-type-summarizer-cli:bazel-cli"), ), }, ) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index d171c48662cf6..da897e858867a 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -102,7 +102,7 @@ RUN set -e ; \ chmod +x /bin/tini RUN mkdir /usr/share/fonts/local -RUN curl -L -o /usr/share/fonts/local/NotoSansCJK-Regular.ttc https://github.com/googlefonts/noto-cjk/raw/NotoSansV2.001/NotoSansCJK-Regular.ttc +RUN curl --retry 8 -S -L -o /usr/share/fonts/local/NotoSansCJK-Regular.ttc https://github.com/googlefonts/noto-cjk/raw/NotoSansV2.001/NotoSansCJK-Regular.ttc RUN echo "5dcd1c336cc9344cb77c03a0cd8982ca8a7dc97d620fd6c9c434e02dcb1ceeb3 /usr/share/fonts/local/NotoSansCJK-Regular.ttc" | sha256sum -c - RUN fc-cache -v diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 834a30ef214c7..175b9c50d4d27 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -21,6 +21,7 @@ export const LICENSE_ALLOWED = [ '(MIT AND Zlib)', '(MIT OR Apache-2.0)', '(MIT OR GPL-3.0)', + '(MIT OR GPL-3.0-or-later)', '(WTFPL OR MIT)', '(MIT OR WTFPL)', '(Unlicense OR Apache-2.0)', diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index ff9ae42d633fb..1c25e53aad184 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -93,7 +93,6 @@ export const PROJECTS = [ 'test/plugin_functional/plugins/*/tsconfig.json', 'test/interpreter_functional/plugins/*/tsconfig.json', 'test/server_integration/__fixtures__/plugins/*/tsconfig.json', - 'packages/kbn-type-summarizer/tests/tsconfig.json', ...BAZEL_PACKAGE_DIRS.map((dir) => `${dir}/*/tsconfig.json`), ]), ]; diff --git a/src/plugins/chart_expressions/expression_xy/common/helpers/table.ts b/src/plugins/chart_expressions/expression_xy/common/helpers/table.ts index a8248226b51c8..65cae152c0caf 100644 --- a/src/plugins/chart_expressions/expression_xy/common/helpers/table.ts +++ b/src/plugins/chart_expressions/expression_xy/common/helpers/table.ts @@ -15,16 +15,15 @@ export function normalizeTable(data: Datatable, xAccessor?: string | ExpressionV const xColumn = xAccessor && getColumnByAccessor(xAccessor, data.columns); if (xColumn && xColumn?.meta.type === 'date') { const xColumnId = xColumn.id; - const rows = data.rows.reduce((normalizedRows, row) => { - return [ - ...normalizedRows, - { - ...row, - [xColumnId]: - typeof row[xColumnId] === 'string' ? moment(row[xColumnId]).valueOf() : row[xColumnId], - }, - ]; - }, []); + if (!data.rows.some((row) => typeof row[xColumnId] === 'string')) return data; + const rows = data.rows.map((row) => { + return typeof row[xColumnId] !== 'string' + ? row + : { + ...row, + [xColumnId]: moment(row[xColumnId]).valueOf(), + }; + }); return { ...data, rows }; } diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx index fe88d651c12f1..fa138fea22e9c 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx @@ -26,6 +26,8 @@ jest.mock('../../../../models/sense_editor', () => { focus: () => {}, }), on: jest.fn(), + addFoldsAtRanges: jest.fn(), + getAllFoldRanges: jest.fn(), }), update: jest.fn(), commands: { diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 9219c6e076ca0..e999c079b490f 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -33,6 +33,7 @@ import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; import { applyCurrentSettings } from './apply_editor_settings'; import { registerCommands } from './keyboard_shortcuts'; import type { SenseEditor } from '../../../../models/sense_editor'; +import { StorageKeys } from '../../../../../services'; const { useUIAceKeyboardMode } = ace; @@ -71,6 +72,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { esHostService, http, autocompleteInfo, + storage, }, docLinkVersion, } = useServicesContext(); @@ -198,6 +200,26 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { } } + function restoreFolds() { + if (editor) { + const foldRanges = storage.get(StorageKeys.FOLDS, []); + editor.getCoreEditor().addFoldsAtRanges(foldRanges); + } + } + + restoreFolds(); + + function saveFoldsOnChange() { + if (editor) { + editor.getCoreEditor().on('changeFold', () => { + const foldRanges = editor.getCoreEditor().getAllFoldRanges(); + storage.set(StorageKeys.FOLDS, foldRanges); + }); + } + } + + saveFoldsOnChange(); + setInputEditor(editor); setTextArea(editorRef.current!.querySelector('textarea')); @@ -223,6 +245,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { settingsService, http, autocompleteInfo, + storage, ]); useEffect(() => { diff --git a/src/plugins/console/public/application/containers/main/main.tsx b/src/plugins/console/public/application/containers/main/main.tsx index 5895b919f9842..2833bf1fbebc3 100644 --- a/src/plugins/console/public/application/containers/main/main.tsx +++ b/src/plugins/console/public/application/containers/main/main.tsx @@ -26,6 +26,7 @@ import { useDataInit } from '../../hooks'; import { getTopNavConfig } from './get_top_nav'; import type { SenseEditor } from '../../models/sense_editor'; +import { getResponseWithMostSevereStatusCode } from '../../../lib/utils'; export function Main() { const { @@ -62,7 +63,7 @@ export function Main() { ); } - const lastDatum = requestData?.[requestData.length - 1] ?? requestError; + const data = getResponseWithMostSevereStatusCode(requestData) ?? requestError; return (
@@ -95,13 +96,13 @@ export function Main() { fold.range); + } + + addFoldsAtRanges(foldRanges: Range[]) { + const session = this.editor.getSession(); + foldRanges.forEach((range) => { + session.addFold('...', _AceRange.fromPoints(range.start, range.end)); + }); + } } diff --git a/src/plugins/console/public/lib/utils/index.ts b/src/plugins/console/public/lib/utils/index.ts index 02d4023282cb2..1beb13eb6b3d4 100644 --- a/src/plugins/console/public/lib/utils/index.ts +++ b/src/plugins/console/public/lib/utils/index.ts @@ -8,6 +8,7 @@ import _ from 'lodash'; import { XJson } from '@kbn/es-ui-shared-plugin/public'; +import type { RequestResult } from '../../application/hooks/use_send_current_request/send_request'; const { collapseLiteralStrings, expandLiteralStrings } = XJson; @@ -94,3 +95,16 @@ export function splitOnUnquotedCommaSpace(s: string) { arr.push(buffer); return arr; } + +/** + * Sorts the request data by statusCode in increasing order and + * returns the last one which will be rendered in network request status bar + */ +export const getResponseWithMostSevereStatusCode = (requestData: RequestResult[] | null) => { + if (requestData) { + return requestData + .slice() + .sort((a, b) => a.response.statusCode - b.response.statusCode) + .pop(); + } +}; diff --git a/src/plugins/console/public/lib/utils/utils.test.js b/src/plugins/console/public/lib/utils/utils.test.js index 738aa5b9bf5c3..8cdd93c3b6ed8 100644 --- a/src/plugins/console/public/lib/utils/utils.test.js +++ b/src/plugins/console/public/lib/utils/utils.test.js @@ -173,4 +173,24 @@ describe('Utils class', () => { ]); }); }); + + test('get response with most severe status code', () => { + expect( + utils.getResponseWithMostSevereStatusCode([ + { response: { statusCode: 500 } }, + { response: { statusCode: 400 } }, + { response: { statusCode: 200 } }, + ]) + ).toEqual({ response: { statusCode: 500 } }); + + expect( + utils.getResponseWithMostSevereStatusCode([ + { response: { statusCode: 0 } }, + { response: { statusCode: 100 } }, + { response: { statusCode: 201 } }, + ]) + ).toEqual({ response: { statusCode: 201 } }); + + expect(utils.getResponseWithMostSevereStatusCode(undefined)).toBe(undefined); + }); }); diff --git a/src/plugins/console/public/services/storage.ts b/src/plugins/console/public/services/storage.ts index 221020e496fec..4d5f897b13403 100644 --- a/src/plugins/console/public/services/storage.ts +++ b/src/plugins/console/public/services/storage.ts @@ -12,6 +12,7 @@ type IStorageEngine = typeof window.localStorage; export enum StorageKeys { WIDTH = 'widths', + FOLDS = 'folds', } export class Storage { diff --git a/src/plugins/console/public/types/core_editor.ts b/src/plugins/console/public/types/core_editor.ts index 700b805aaecc0..aa048256421bb 100644 --- a/src/plugins/console/public/types/core_editor.ts +++ b/src/plugins/console/public/types/core_editor.ts @@ -17,7 +17,8 @@ export type EditorEvent = | 'changeCursor' | 'changeScrollTop' | 'change' - | 'changeSelection'; + | 'changeSelection' + | 'changeFold'; export type AutoCompleterFunction = ( pos: Position, @@ -277,4 +278,14 @@ export interface CoreEditor { * Indent document within request range */ autoIndent(reqRange: Range): void; + + /* + * Get all fold ranges in document + */ + getAllFoldRanges(): Range[]; + + /** + * Add folds at given ranges + */ + addFoldsAtRanges(foldRanges: Range[]): void; } diff --git a/src/plugins/console/server/lib/proxy_request.test.ts b/src/plugins/console/server/lib/proxy_request.test.ts index 2bb5e481fbb26..98c63d9685c87 100644 --- a/src/plugins/console/server/lib/proxy_request.test.ts +++ b/src/plugins/console/server/lib/proxy_request.test.ts @@ -31,7 +31,7 @@ describe(`Console's send request`, () => { it('correctly implements timeout and abort mechanism', async () => { fakeRequest = { - abort: sinon.stub(), + destroy: sinon.stub(), on() {}, once() {}, } as any; @@ -47,7 +47,7 @@ describe(`Console's send request`, () => { fail('Should not reach here!'); } catch (e) { expect(e.message).toEqual('Client request timeout'); - expect((fakeRequest.abort as sinon.SinonStub).calledOnce).toBe(true); + expect((fakeRequest.destroy as sinon.SinonStub).calledOnce).toBe(true); } }); diff --git a/src/plugins/console/server/lib/proxy_request.ts b/src/plugins/console/server/lib/proxy_request.ts index c4fbfd315da4e..4a8839d1d8583 100644 --- a/src/plugins/console/server/lib/proxy_request.ts +++ b/src/plugins/console/server/lib/proxy_request.ts @@ -113,7 +113,10 @@ export const proxyRequest = ({ const timeoutPromise = new Promise((timeoutResolve, timeoutReject) => { setTimeout(() => { - if (!req.aborted && !req.socket) req.abort(); + // Destroy the stream on timeout and close the connection. + if (!req.destroyed) { + req.destroy(); + } if (!resolved) { timeoutReject(Boom.gatewayTimeout('Client request timeout')); } else { diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 7474b32d3e272..773791ffef3ad 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -7,13 +7,14 @@ */ import { History } from 'history'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { useKibana, useExecutionContext } from '@kbn/kibana-react-plugin/public'; import { useDashboardSelector } from './state'; import { useDashboardAppState } from './hooks'; import { + dashboardFeatureCatalog, getDashboardBreadcrumb, getDashboardTitle, leaveConfirmStrings, @@ -49,6 +50,7 @@ export function DashboardApp({ } = useKibana().services; const [showNoDataPage, setShowNoDataPage] = useState(false); + const dashboardTitleRef = useRef(null); const kbnUrlStateStorage = useMemo( () => @@ -76,6 +78,15 @@ export function DashboardApp({ isEmbeddedExternally: Boolean(embedSettings), }); + // focus on the top header when title or view mode is changed + useEffect(() => { + dashboardTitleRef.current?.focus(); + }, [dashboardState.title, dashboardState.viewMode]); + + const dashboardTitle = useMemo(() => { + return getDashboardTitle(dashboardState.title, dashboardState.viewMode, !savedDashboardId); + }, [dashboardState.title, dashboardState.viewMode, savedDashboardId]); + // Build app leave handler whenever hasUnsavedChanges changes useEffect(() => { onAppLeave((actions) => { @@ -108,10 +119,10 @@ export function DashboardApp({ }, }, { - text: getDashboardTitle(dashboardState.title, dashboardState.viewMode, !savedDashboardId), + text: dashboardTitle, }, ]); - }, [chrome, dashboardState.title, dashboardState.viewMode, redirectTo, savedDashboardId]); + }, [chrome, dashboardState.title, redirectTo, savedDashboardId, dashboardTitle]); // clear search session when leaving dashboard route useEffect(() => { @@ -131,6 +142,12 @@ export function DashboardApp({ return ( <> +

{`${dashboardFeatureCatalog.getTitle()} - ${dashboardTitle}`}

{showNoDataPage && ( setShowNoDataPage(false)} /> )} diff --git a/src/plugins/data/common/search/aggs/agg_config.ts b/src/plugins/data/common/search/aggs/agg_config.ts index e47fc12eca446..5c13747b238c7 100644 --- a/src/plugins/data/common/search/aggs/agg_config.ts +++ b/src/plugins/data/common/search/aggs/agg_config.ts @@ -152,7 +152,7 @@ export class AggConfig { const isDeserialized = isType || isObject; if (!isDeserialized) { - val = aggParam.deserialize(val, this); + val = aggParam.deserialize(_.cloneDeep(val), this); } to[aggParam.name] = val; diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index d9d8c44d56b80..ad334470205aa 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -177,7 +177,7 @@ export const getSavedObjects = (): SavedObject[] => [ uiStateJSON: '{}', version: 1, visState: - '{"title":"[eCommerce] Markdown","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":false,"markdown":"### Sample eCommerce Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"aggs":[]}', + '{"title":"[eCommerce] Markdown","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":false,"markdown":"## Sample eCommerce Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"aggs":[]}', }, coreMigrationVersion: '8.0.0', id: 'c00d1f90-f5ea-11eb-a78e-83aac3c38a60', diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 6180db51c4dd3..2e4413dcba0d3 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -387,7 +387,7 @@ export const getSavedObjects = (): SavedObject[] => [ 'Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats', }), panelsJSON: - '[{"version":"7.14.0","type":"search","gridData":{"x":0,"y":68,"w":48,"h":15,"i":"4"},"panelIndex":"4","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_4"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":15,"w":24,"h":9,"i":"7"},"panelIndex":"7","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_7"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":57,"w":24,"h":11,"i":"10"},"panelIndex":"10","embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false},"enhancements":{}},"panelRefName":"panel_10"},{"version":"7.14.0","type":"visualization","gridData":{"x":36,"y":57,"w":12,"h":11,"i":"21"},"panelIndex":"21","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_21"},{"version":"7.14.0","type":"map","gridData":{"x":0,"y":35,"w":24,"h":22,"i":"23"},"panelIndex":"23","embeddableConfig":{"isLayerTOCOpen":true,"enhancements":{},"mapCenter":{"lat":34.65823,"lon":-112.44472,"zoom":4.28},"mapBuffer":{"minLon":-135,"minLat":21.94305,"maxLon":-90,"maxLat":48.9225},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_23"},{"version":"7.14.0","type":"visualization","gridData":{"x":24,"y":35,"w":24,"h":22,"i":"31"},"panelIndex":"31","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_31"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":0,"w":32,"h":7,"i":"aa810aa2-29c9-4a75-b39e-f4f267de1732"},"panelIndex":"aa810aa2-29c9-4a75-b39e-f4f267de1732","embeddableConfig":{"savedVis":{"title":"[Flights] Controls","description":"","type":"input_control_vis","params":{"controls":[{"id":"1525098134264","fieldName":"OriginCityName","parent":"","label":"Origin City","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_0_index_pattern"},{"id":"1525099277699","fieldName":"DestCityName","parent":"1525098134264","label":"Destination City","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_1_index_pattern"},{"id":"1525099307278","fieldName":"AvgTicketPrice","parent":"","label":"Average Ticket Price","type":"range","options":{"decimalPlaces":0,"step":10},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_2_index_pattern"}],"updateFiltersOnChange":false,"useTimeFilter":true,"pinFilters":false},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"7.14.0","type":"visualization","gridData":{"x":32,"y":0,"w":16,"h":7,"i":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9"},"panelIndex":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9","embeddableConfig":{"savedVis":{"title":"[Flights] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":10,"openLinksInNewTab":true,"markdown":"### Sample Flight data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":8,"i":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65"},"panelIndex":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"03c34665-471c-49c7-acf1-5a11f517421c":{"columns":{"a5b94e30-4e77-4b0a-9187-1d8b13de1456":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"3e267327-7317-4310-aee3-320e0f7c1e70":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["a5b94e30-4e77-4b0a-9187-1d8b13de1456","3e267327-7317-4310-aee3-320e0f7c1e70"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"right"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"custom","lowerBound":0,"upperBound":1},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"layerId":"03c34665-471c-49c7-acf1-5a11f517421c","accessors":["3e267327-7317-4310-aee3-320e0f7c1e70"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"a5b94e30-4e77-4b0a-9187-1d8b13de1456"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-03c34665-471c-49c7-acf1-5a11f517421c"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Flight count"},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":7,"w":8,"h":8,"i":"392b4936-f753-47bc-a98d-a4e41a0a4cd4"},"panelIndex":"392b4936-f753-47bc-a98d-a4e41a0a4cd4","embeddableConfig":{"enhancements":{},"attributes":{"title":"[Flights] Total Flights","description":"","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"8fa993db-c147-4954-adf7-4ff264d42576":{"columns":{"81124c45-6ab6-42f4-8859-495d55eb8065":{"label":"Total flights","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true}},"columnOrder":["81124c45-6ab6-42f4-8859-495d55eb8065"],"incompleteColumns":{}}}}},"visualization":{"layerId":"8fa993db-c147-4954-adf7-4ff264d42576","accessor":"81124c45-6ab6-42f4-8859-495d55eb8065"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-8fa993db-c147-4954-adf7-4ff264d42576"}]},"hidePanelTitles":true}},{"version":"7.14.0","type":"lens","gridData":{"x":32,"y":7,"w":8,"h":4,"i":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b"},"panelIndex":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"FlightDelay : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":41},"text":"count(kql=\'FlightDelay : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Delayed","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":40,"y":7,"w":8,"h":4,"i":"aa591c29-1a31-4ee1-a71d-b829c06fd162"},"panelIndex":"aa591c29-1a31-4ee1-a71d-b829c06fd162","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Delayed vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"FlightDelay","params":{"query":true},"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelay":true}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":32,"y":11,"w":8,"h":4,"i":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2"},"panelIndex":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"Cancelled : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of Cancelled","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":39},"text":"count(kql=\'Cancelled : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Cancelled","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":40,"y":11,"w":8,"h":4,"i":"2e33ade5-96e5-40b4-b460-493e5d4fa834"},"panelIndex":"2e33ade5-96e5-40b4-b460-493e5d4fa834","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Cancelled vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"Cancelled","params":{"query":true},"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"Cancelled":true}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":15,"w":24,"h":20,"i":"fb86b32f-fb7a-45cf-9511-f366fef51bbd"},"panelIndex":"fb86b32f-fb7a-45cf-9511-f366fef51bbd","embeddableConfig":{"attributes":{"title":"Cities by delay, cancellation","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"f26e8f7a-4118-4227-bea0-5c02d8b270f7":{"columns":{"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0":{"label":"Top values of OriginCityName","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"OriginCityName","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"alphabetical","fallback":true},"orderDirection":"asc","otherBucket":true,"missingBucket":false}},"52f6f2e9-6242-4c44-be63-b799150e7e60X0":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"FlightDelay : true ","language":"kuery"},"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X1":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X2":{"label":"Part of Delay %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"location":{"min":0,"max":42},"text":"count(kql=\'FlightDelay : true \') / count()"}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60":{"label":"Delay %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true \') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X2"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"Cancelled: true","language":"kuery"},"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2":{"label":"Part of Cancel %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"location":{"min":0,"max":38},"text":"count(kql=\'Cancelled: true\') / count()"}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6":{"label":"Cancel %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled: true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2"],"customLabel":true}},"columnOrder":["3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","52f6f2e9-6242-4c44-be63-b799150e7e60","52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1","52f6f2e9-6242-4c44-be63-b799150e7e60X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6"],"incompleteColumns":{}}}}},"visualization":{"columns":[{"isTransposed":false,"columnId":"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","width":262.75},{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","isTransposed":false,"width":302.5,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":1}],"name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeType":"number","rangeMin":0.2,"rangeMax":0.6}},"alignment":"center"},{"columnId":"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6","isTransposed":false,"alignment":"center","colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":0.6666666666666666}],"rangeType":"number","name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeMin":0.2,"rangeMax":0.6}}}],"layerId":"f26e8f7a-4118-4227-bea0-5c02d8b270f7","sorting":{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","direction":"desc"}},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-f26e8f7a-4118-4227-bea0-5c02d8b270f7"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Most delayed cities"},{"version":"7.14.0","type":"lens","gridData":{"x":0,"y":24,"w":24,"h":11,"i":"0cc42484-16f7-42ec-b38c-9bf8be69cde7"},"panelIndex":"0cc42484-16f7-42ec-b38c-9bf8be69cde7","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"e80cc05e-c52a-4e5f-ac71-4b37274867f5":{"columns":{"caf7421e-93a3-439e-ab0a-fbdead93c21c":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"0233d302-ec81-4fbe-96cb-7fac84cf035c"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"13ec79e3-9d73-4536-9056-3d92802bb30a":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"0233d302-ec81-4fbe-96cb-7fac84cf035c":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["caf7421e-93a3-439e-ab0a-fbdead93c21c","13ec79e3-9d73-4536-9056-3d92802bb30a","0233d302-ec81-4fbe-96cb-7fac84cf035c"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"bottom"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":true,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_percentage_stacked","layers":[{"layerId":"e80cc05e-c52a-4e5f-ac71-4b37274867f5","accessors":["0233d302-ec81-4fbe-96cb-7fac84cf035c"],"position":"top","seriesType":"bar_percentage_stacked","showGridlines":false,"palette":{"type":"palette","name":"cool"},"xAccessor":"13ec79e3-9d73-4536-9056-3d92802bb30a","splitAccessor":"caf7421e-93a3-439e-ab0a-fbdead93c21c"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-e80cc05e-c52a-4e5f-ac71-4b37274867f5"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Delay Type"},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":57,"w":12,"h":11,"i":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0"},"panelIndex":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsPie","state":{"datasourceStates":{"indexpattern":{"layers":{"0c8e136b-a822-4fb3-836d-e06cbea4eea4":{"columns":{"d1cee8bf-34cf-4141-99d7-ff043ee77b56":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"aa152ace-ee2d-447b-b86d-459bef4d7880"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"aa152ace-ee2d-447b-b86d-459bef4d7880":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["d1cee8bf-34cf-4141-99d7-ff043ee77b56","aa152ace-ee2d-447b-b86d-459bef4d7880"],"incompleteColumns":{}}}}},"visualization":{"shape":"pie","palette":{"type":"palette","name":"cool"},"layers":[{"layerId":"0c8e136b-a822-4fb3-836d-e06cbea4eea4","groups":["d1cee8bf-34cf-4141-99d7-ff043ee77b56"],"metric":"aa152ace-ee2d-447b-b86d-459bef4d7880","numberDisplay":"percent","categoryDisplay":"default","legendDisplay":"default","nestedLegend":false}]},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"type":"phrase","key":"FlightDelayType","params":{"query":"No Delay"},"disabled":false,"negate":true,"alias":null,"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelayType":"No Delay"}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-0c8e136b-a822-4fb3-836d-e06cbea4eea4"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Delay Type"}]', + '[{"version":"7.14.0","type":"search","gridData":{"x":0,"y":68,"w":48,"h":15,"i":"4"},"panelIndex":"4","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_4"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":15,"w":24,"h":9,"i":"7"},"panelIndex":"7","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_7"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":57,"w":24,"h":11,"i":"10"},"panelIndex":"10","embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false},"enhancements":{}},"panelRefName":"panel_10"},{"version":"7.14.0","type":"visualization","gridData":{"x":36,"y":57,"w":12,"h":11,"i":"21"},"panelIndex":"21","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_21"},{"version":"7.14.0","type":"map","gridData":{"x":0,"y":35,"w":24,"h":22,"i":"23"},"panelIndex":"23","embeddableConfig":{"isLayerTOCOpen":true,"enhancements":{},"mapCenter":{"lat":34.65823,"lon":-112.44472,"zoom":4.28},"mapBuffer":{"minLon":-135,"minLat":21.94305,"maxLon":-90,"maxLat":48.9225},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_23"},{"version":"7.14.0","type":"visualization","gridData":{"x":24,"y":35,"w":24,"h":22,"i":"31"},"panelIndex":"31","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_31"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":0,"w":32,"h":7,"i":"aa810aa2-29c9-4a75-b39e-f4f267de1732"},"panelIndex":"aa810aa2-29c9-4a75-b39e-f4f267de1732","embeddableConfig":{"savedVis":{"title":"[Flights] Controls","description":"","type":"input_control_vis","params":{"controls":[{"id":"1525098134264","fieldName":"OriginCityName","parent":"","label":"Origin City","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_0_index_pattern"},{"id":"1525099277699","fieldName":"DestCityName","parent":"1525098134264","label":"Destination City","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_1_index_pattern"},{"id":"1525099307278","fieldName":"AvgTicketPrice","parent":"","label":"Average Ticket Price","type":"range","options":{"decimalPlaces":0,"step":10},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_2_index_pattern"}],"updateFiltersOnChange":false,"useTimeFilter":true,"pinFilters":false},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"7.14.0","type":"visualization","gridData":{"x":32,"y":0,"w":16,"h":7,"i":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9"},"panelIndex":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9","embeddableConfig":{"savedVis":{"title":"[Flights] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":10,"openLinksInNewTab":true,"markdown":"## Sample Flight data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":8,"i":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65"},"panelIndex":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"03c34665-471c-49c7-acf1-5a11f517421c":{"columns":{"a5b94e30-4e77-4b0a-9187-1d8b13de1456":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"3e267327-7317-4310-aee3-320e0f7c1e70":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["a5b94e30-4e77-4b0a-9187-1d8b13de1456","3e267327-7317-4310-aee3-320e0f7c1e70"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"right"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"custom","lowerBound":0,"upperBound":1},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"layerId":"03c34665-471c-49c7-acf1-5a11f517421c","accessors":["3e267327-7317-4310-aee3-320e0f7c1e70"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"a5b94e30-4e77-4b0a-9187-1d8b13de1456"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-03c34665-471c-49c7-acf1-5a11f517421c"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Flight count"},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":7,"w":8,"h":8,"i":"392b4936-f753-47bc-a98d-a4e41a0a4cd4"},"panelIndex":"392b4936-f753-47bc-a98d-a4e41a0a4cd4","embeddableConfig":{"enhancements":{},"attributes":{"title":"[Flights] Total Flights","description":"","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"8fa993db-c147-4954-adf7-4ff264d42576":{"columns":{"81124c45-6ab6-42f4-8859-495d55eb8065":{"label":"Total flights","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true}},"columnOrder":["81124c45-6ab6-42f4-8859-495d55eb8065"],"incompleteColumns":{}}}}},"visualization":{"layerId":"8fa993db-c147-4954-adf7-4ff264d42576","accessor":"81124c45-6ab6-42f4-8859-495d55eb8065"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-8fa993db-c147-4954-adf7-4ff264d42576"}]},"hidePanelTitles":true}},{"version":"7.14.0","type":"lens","gridData":{"x":32,"y":7,"w":8,"h":4,"i":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b"},"panelIndex":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"FlightDelay : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":41},"text":"count(kql=\'FlightDelay : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Delayed","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":40,"y":7,"w":8,"h":4,"i":"aa591c29-1a31-4ee1-a71d-b829c06fd162"},"panelIndex":"aa591c29-1a31-4ee1-a71d-b829c06fd162","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Delayed vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"FlightDelay","params":{"query":true},"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelay":true}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":32,"y":11,"w":8,"h":4,"i":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2"},"panelIndex":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"Cancelled : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of Cancelled","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":39},"text":"count(kql=\'Cancelled : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Cancelled","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":40,"y":11,"w":8,"h":4,"i":"2e33ade5-96e5-40b4-b460-493e5d4fa834"},"panelIndex":"2e33ade5-96e5-40b4-b460-493e5d4fa834","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Cancelled vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"Cancelled","params":{"query":true},"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"Cancelled":true}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":15,"w":24,"h":20,"i":"fb86b32f-fb7a-45cf-9511-f366fef51bbd"},"panelIndex":"fb86b32f-fb7a-45cf-9511-f366fef51bbd","embeddableConfig":{"attributes":{"title":"Cities by delay, cancellation","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"f26e8f7a-4118-4227-bea0-5c02d8b270f7":{"columns":{"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0":{"label":"Top values of OriginCityName","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"OriginCityName","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"alphabetical","fallback":true},"orderDirection":"asc","otherBucket":true,"missingBucket":false}},"52f6f2e9-6242-4c44-be63-b799150e7e60X0":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"FlightDelay : true ","language":"kuery"},"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X1":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X2":{"label":"Part of Delay %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"location":{"min":0,"max":42},"text":"count(kql=\'FlightDelay : true \') / count()"}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60":{"label":"Delay %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true \') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X2"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"Cancelled: true","language":"kuery"},"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2":{"label":"Part of Cancel %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"location":{"min":0,"max":38},"text":"count(kql=\'Cancelled: true\') / count()"}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6":{"label":"Cancel %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled: true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2"],"customLabel":true}},"columnOrder":["3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","52f6f2e9-6242-4c44-be63-b799150e7e60","52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1","52f6f2e9-6242-4c44-be63-b799150e7e60X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6"],"incompleteColumns":{}}}}},"visualization":{"columns":[{"isTransposed":false,"columnId":"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","width":262.75},{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","isTransposed":false,"width":302.5,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":1}],"name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeType":"number","rangeMin":0.2,"rangeMax":0.6}},"alignment":"center"},{"columnId":"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6","isTransposed":false,"alignment":"center","colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":0.6666666666666666}],"rangeType":"number","name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeMin":0.2,"rangeMax":0.6}}}],"layerId":"f26e8f7a-4118-4227-bea0-5c02d8b270f7","sorting":{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","direction":"desc"}},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-f26e8f7a-4118-4227-bea0-5c02d8b270f7"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Most delayed cities"},{"version":"7.14.0","type":"lens","gridData":{"x":0,"y":24,"w":24,"h":11,"i":"0cc42484-16f7-42ec-b38c-9bf8be69cde7"},"panelIndex":"0cc42484-16f7-42ec-b38c-9bf8be69cde7","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"e80cc05e-c52a-4e5f-ac71-4b37274867f5":{"columns":{"caf7421e-93a3-439e-ab0a-fbdead93c21c":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"0233d302-ec81-4fbe-96cb-7fac84cf035c"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"13ec79e3-9d73-4536-9056-3d92802bb30a":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"0233d302-ec81-4fbe-96cb-7fac84cf035c":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["caf7421e-93a3-439e-ab0a-fbdead93c21c","13ec79e3-9d73-4536-9056-3d92802bb30a","0233d302-ec81-4fbe-96cb-7fac84cf035c"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"bottom"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":true,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_percentage_stacked","layers":[{"layerId":"e80cc05e-c52a-4e5f-ac71-4b37274867f5","accessors":["0233d302-ec81-4fbe-96cb-7fac84cf035c"],"position":"top","seriesType":"bar_percentage_stacked","showGridlines":false,"palette":{"type":"palette","name":"cool"},"xAccessor":"13ec79e3-9d73-4536-9056-3d92802bb30a","splitAccessor":"caf7421e-93a3-439e-ab0a-fbdead93c21c"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-e80cc05e-c52a-4e5f-ac71-4b37274867f5"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Delay Type"},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":57,"w":12,"h":11,"i":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0"},"panelIndex":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsPie","state":{"datasourceStates":{"indexpattern":{"layers":{"0c8e136b-a822-4fb3-836d-e06cbea4eea4":{"columns":{"d1cee8bf-34cf-4141-99d7-ff043ee77b56":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"aa152ace-ee2d-447b-b86d-459bef4d7880"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"aa152ace-ee2d-447b-b86d-459bef4d7880":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["d1cee8bf-34cf-4141-99d7-ff043ee77b56","aa152ace-ee2d-447b-b86d-459bef4d7880"],"incompleteColumns":{}}}}},"visualization":{"shape":"pie","palette":{"type":"palette","name":"cool"},"layers":[{"layerId":"0c8e136b-a822-4fb3-836d-e06cbea4eea4","groups":["d1cee8bf-34cf-4141-99d7-ff043ee77b56"],"metric":"aa152ace-ee2d-447b-b86d-459bef4d7880","numberDisplay":"percent","categoryDisplay":"default","legendDisplay":"default","nestedLegend":false}]},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"type":"phrase","key":"FlightDelayType","params":{"query":"No Delay"},"disabled":false,"negate":true,"alias":null,"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelayType":"No Delay"}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-0c8e136b-a822-4fb3-836d-e06cbea4eea4"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Delay Type"}]', optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', version: 1, timeRestore: true, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 55ed75f7bcef3..b5f57e0edb2e6 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -487,7 +487,7 @@ export const getSavedObjects = (): SavedObject[] => [ defaultMessage: "Analyze mock web traffic log data for Elastic's website", }), panelsJSON: - '[{"version":"8.0.0","type":"map","gridData":{"x":0,"y":19,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"mapBuffer":{"minLon":-112.5,"minLat":21.94305,"maxLon":-45,"maxLat":55.77657},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_4"},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":52,"w":24,"h":13,"i":"9"},"panelIndex":"9","embeddableConfig":{"mapCenter":[36.8092847020594,-96.94335937500001],"vis":{"params":{"sort":{"columnIndex":null,"direction":null}}},"enhancements":{}},"panelRefName":"panel_9"},{"version":"8.0.0","type":"visualization","gridData":{"x":12,"y":6,"w":12,"h":8,"i":"11"},"panelIndex":"11","embeddableConfig":{"vis":{"colors":{"0 - 500":"#BF1B00","1000 - 1500":"#7EB26D","500 - 1000":"#F2C96D"},"defaultColors":{"0 - 500":"rgb(165,0,38)","1000 - 1500":"rgb(0,104,55)","500 - 1000":"rgb(255,255,190)"},"legendOpen":false},"enhancements":{}},"title":"","panelRefName":"panel_11"},{"version":"8.0.0","type":"visualization","gridData":{"x":24,"y":19,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.0.0","type":"visualization","gridData":{"x":24,"y":6,"w":24,"h":13,"i":"15"},"panelIndex":"15","embeddableConfig":{"enhancements":{"dynamicActions":{"events":[]}}},"panelRefName":"panel_15"},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":0,"w":17,"h":6,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"### Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.0.0","type":"visualization","gridData":{"x":17,"y":0,"w":31,"h":6,"i":"30326cdb-4ddd-49eb-a4f1-b555caa21d7c"},"panelIndex":"30326cdb-4ddd-49eb-a4f1-b555caa21d7c","embeddableConfig":{"savedVis":{"title":"[Logs] Input Controls","description":"","type":"input_control_vis","params":{"controls":[{"id":"1523980210832","fieldName":"geo.src","label":"Source Country","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"parent":"","indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_0_index_pattern"},{"id":"1523980191978","fieldName":"machine.os.keyword","label":"OS","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"parent":"1523980210832","indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_1_index_pattern"},{"id":"1523980232790","fieldName":"bytes","label":"Bytes","type":"range","options":{"decimalPlaces":0,"step":1024},"indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_2_index_pattern"}],"updateFiltersOnChange":true,"useTimeFilter":true,"pinFilters":false},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":6,"w":12,"h":8,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","accessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}]},"enhancements":{}}},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":14,"w":12,"h":5,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}]},"enhancements":{}}},{"version":"8.0.0","type":"lens","gridData":{"x":12,"y":14,"w":12,"h":5,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}]},"enhancements":{}}},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":37,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":65,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"}},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"},{"version":"8.0.0","type":"lens","gridData":{"x":24,"y":52,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"}]', + '[{"version":"8.0.0","type":"map","gridData":{"x":0,"y":19,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"mapBuffer":{"minLon":-112.5,"minLat":21.94305,"maxLon":-45,"maxLat":55.77657},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_4"},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":52,"w":24,"h":13,"i":"9"},"panelIndex":"9","embeddableConfig":{"mapCenter":[36.8092847020594,-96.94335937500001],"vis":{"params":{"sort":{"columnIndex":null,"direction":null}}},"enhancements":{}},"panelRefName":"panel_9"},{"version":"8.0.0","type":"visualization","gridData":{"x":12,"y":6,"w":12,"h":8,"i":"11"},"panelIndex":"11","embeddableConfig":{"vis":{"colors":{"0 - 500":"#BF1B00","1000 - 1500":"#7EB26D","500 - 1000":"#F2C96D"},"defaultColors":{"0 - 500":"rgb(165,0,38)","1000 - 1500":"rgb(0,104,55)","500 - 1000":"rgb(255,255,190)"},"legendOpen":false},"enhancements":{}},"title":"","panelRefName":"panel_11"},{"version":"8.0.0","type":"visualization","gridData":{"x":24,"y":19,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.0.0","type":"visualization","gridData":{"x":24,"y":6,"w":24,"h":13,"i":"15"},"panelIndex":"15","embeddableConfig":{"enhancements":{"dynamicActions":{"events":[]}}},"panelRefName":"panel_15"},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":0,"w":17,"h":6,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.0.0","type":"visualization","gridData":{"x":17,"y":0,"w":31,"h":6,"i":"30326cdb-4ddd-49eb-a4f1-b555caa21d7c"},"panelIndex":"30326cdb-4ddd-49eb-a4f1-b555caa21d7c","embeddableConfig":{"savedVis":{"title":"[Logs] Input Controls","description":"","type":"input_control_vis","params":{"controls":[{"id":"1523980210832","fieldName":"geo.src","label":"Source Country","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"parent":"","indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_0_index_pattern"},{"id":"1523980191978","fieldName":"machine.os.keyword","label":"OS","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"parent":"1523980210832","indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_1_index_pattern"},{"id":"1523980232790","fieldName":"bytes","label":"Bytes","type":"range","options":{"decimalPlaces":0,"step":1024},"indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_2_index_pattern"}],"updateFiltersOnChange":true,"useTimeFilter":true,"pinFilters":false},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":6,"w":12,"h":8,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","accessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}]},"enhancements":{}}},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":14,"w":12,"h":5,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}]},"enhancements":{}}},{"version":"8.0.0","type":"lens","gridData":{"x":12,"y":14,"w":12,"h":5,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}]},"enhancements":{}}},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":37,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":65,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"}},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"},{"version":"8.0.0","type":"lens","gridData":{"x":24,"y":52,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"}]', optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', version: 2, timeRestore: true, diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts index a83ee7a57c432..fb5c9549fefd5 100644 --- a/src/plugins/home/server/services/sample_data/routes/list.ts +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { sortBy } from 'lodash'; import type { IRouter, Logger, RequestHandlerContext } from '@kbn/core/server'; import type { AppLinkData, SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { createIndexName } from '../lib/create_index_name'; @@ -55,7 +56,7 @@ export const createListRoute = ( previewImagePath: sampleDataset.previewImagePath, darkPreviewImagePath: sampleDataset.darkPreviewImagePath, overviewDashboard: findObjectId('dashboard', sampleDataset.overviewDashboard), - appLinks, + appLinks: sortBy(appLinks, 'order'), defaultIndex: findObjectId('index-pattern', sampleDataset.defaultIndex), dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })), ...sampleDataStatus, diff --git a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts index 66e1a7099f7eb..a5673fdc12a2f 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts @@ -8,7 +8,7 @@ import { get } from 'lodash'; import moment from 'moment'; -import { SearchResponse } from '@kbn/core/server'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { CollectorFetchContext } from '@kbn/usage-collection-plugin/server'; interface SearchHit { diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/fetch_all_saved_objects.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/fetch_all_saved_objects.ts new file mode 100644 index 0000000000000..261b1dc0adb00 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/fetch_all_saved_objects.ts @@ -0,0 +1,28 @@ +/* + * 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 { + ISavedObjectsRepository, + SavedObjectsCreatePointInTimeFinderOptions, + SavedObjectsFindResult, +} from '@kbn/core/server'; + +export async function fetchAllSavedObjects( + soRepository: ISavedObjectsRepository, + findOptions: SavedObjectsCreatePointInTimeFinderOptions +): Promise>> { + const finder = soRepository.createPointInTimeFinder({ ...findOptions, perPage: 1000 }); + + const allSavedObjects: Array> = []; + + for await (const { saved_objects: savedObjects } of finder.find()) { + allSavedObjects.push(...savedObjects); + } + + return allSavedObjects; +} diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts index 6c55d0f7535af..c8a83deba52e8 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/rollups/total.ts @@ -16,6 +16,7 @@ import { SAVED_OBJECTS_TOTAL_TYPE, } from '../saved_objects_types'; import { serializeKey } from './utils'; +import { fetchAllSavedObjects } from '../fetch_all_saved_objects'; /** * Moves all the daily documents into aggregated "total" documents as we don't care about any granularity after 90 days @@ -28,16 +29,11 @@ export async function rollTotals(logger: Logger, savedObjectsClient?: ISavedObje } try { - const [ - { saved_objects: rawApplicationUsageTotals }, - { saved_objects: rawApplicationUsageDaily }, - ] = await Promise.all([ - savedObjectsClient.find({ - perPage: 10000, + const [rawApplicationUsageTotals, rawApplicationUsageDaily] = await Promise.all([ + fetchAllSavedObjects(savedObjectsClient, { type: SAVED_OBJECTS_TOTAL_TYPE, }), - savedObjectsClient.find({ - perPage: 10000, + fetchAllSavedObjects(savedObjectsClient, { type: SAVED_OBJECTS_DAILY_TYPE, filter: `${SAVED_OBJECTS_DAILY_TYPE}.attributes.timestamp < now-90d`, }), diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts index 35f649f4e4e21..fdf6e99f7280d 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/saved_objects_types.ts @@ -27,12 +27,6 @@ export interface ApplicationUsageTransactional extends ApplicationUsageTotal { timestamp: string; } -/** - * @deprecated transactional type is no longer used, and only preserved for backward compatibility - * @removeBy 8.0.0 - */ -export const SAVED_OBJECTS_TRANSACTIONAL_TYPE = 'application_usage_transactional'; - /** * Used to aggregate the transactional events into daily summaries so we can purge the granular events */ @@ -66,16 +60,4 @@ export function registerMappings(registerType: SavedObjectsServiceSetup['registe }, }, }); - - // Type for storing ApplicationUsageTransactional (declaring empty mappings because we don't use the internal fields for query/aggregations) - // Remark: this type is deprecated and only here for BWC reasons. - registerType({ - name: SAVED_OBJECTS_TRANSACTIONAL_TYPE, - hidden: false, - namespaceType: 'agnostic', - mappings: { - dynamic: false, - properties: {}, - }, - }); } diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts index b582cd3ceff64..1d93a05ed9d09 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts @@ -8,12 +8,12 @@ import moment from 'moment'; import { type Observable, takeUntil, timer } from 'rxjs'; -import { ISavedObjectsRepository, Logger, SavedObjectsServiceSetup } from '@kbn/core/server'; -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import type { ISavedObjectsRepository, Logger, SavedObjectsServiceSetup } from '@kbn/core/server'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { MAIN_APP_DEFAULT_VIEW_ID } from '@kbn/usage-collection-plugin/common/constants'; import { - ApplicationUsageDaily, - ApplicationUsageTotal, + type ApplicationUsageDaily, + type ApplicationUsageTotal, registerMappings, SAVED_OBJECTS_DAILY_TYPE, SAVED_OBJECTS_TOTAL_TYPE, @@ -21,7 +21,8 @@ import { import { applicationUsageSchema } from './schema'; import { rollTotals, serializeKey } from './rollups'; import { ROLL_TOTAL_INDICES_INTERVAL, ROLL_INDICES_START } from './constants'; -import { ApplicationUsageTelemetryReport, ApplicationUsageViews } from './types'; +import type { ApplicationUsageTelemetryReport, ApplicationUsageViews } from './types'; +import { fetchAllSavedObjects } from './fetch_all_saved_objects'; export const transformByApplicationViews = ( report: ApplicationUsageViews @@ -67,17 +68,12 @@ export function registerApplicationUsageCollector( if (typeof savedObjectsClient === 'undefined') { return; } - const [ - { saved_objects: rawApplicationUsageTotals }, - { saved_objects: rawApplicationUsageDaily }, - ] = await Promise.all([ - savedObjectsClient.find({ + const [rawApplicationUsageTotals, rawApplicationUsageDaily] = await Promise.all([ + fetchAllSavedObjects(savedObjectsClient, { type: SAVED_OBJECTS_TOTAL_TYPE, - perPage: 10000, // We only have 44 apps for now. This limit is OK. }), - savedObjectsClient.find({ + fetchAllSavedObjects(savedObjectsClient, { type: SAVED_OBJECTS_DAILY_TYPE, - perPage: 10000, // We can have up to 44 apps * 91 days = 4004 docs. This limit is OK }), ]); diff --git a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts index 8910279ab4ab4..379431d676cf6 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/event_loop_delays/rollups/integration_tests/daily_rollups.test.ts @@ -23,10 +23,8 @@ import { } from '../../saved_objects'; import moment from 'moment'; -const { startES } = createTestServers({ - adjustTimeout: (t: number) => jest.setTimeout(t), -}); const eventLoopDelaysMonitor = metricsServiceMock.createEventLoopDelaysMonitor(); + function createRawObject(date: moment.MomentInput) { const pid = Math.round(Math.random() * 10000); const instanceUuid = 'mock_instance'; @@ -56,21 +54,24 @@ const outdatedRawEventLoopDelaysDaily = [ createRawObject(moment().subtract(7, 'days')), ]; -// FLAKY https://github.com/elastic/kibana/issues/111821 -describe.skip('daily rollups integration test', () => { +describe(`daily rollups integration test`, () => { let esServer: TestElasticsearchUtils; let root: TestKibanaUtils['root']; let internalRepository: ISavedObjectsRepository; let logger: Logger; beforeAll(async () => { + const { startES } = createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), + }); + esServer = await startES(); root = createRootWithCorePlugins(); await root.preboot(); await root.setup(); const start = await root.start(); - logger = root.logger.get('test dailt rollups'); + logger = root.logger.get('test daily rollups'); internalRepository = start.savedObjects.createInternalRepository([SAVED_OBJECTS_DAILY_TYPE]); await internalRepository.bulkCreate( diff --git a/src/plugins/kibana_usage_collection/server/collectors/ui_counters/register_ui_counters_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/ui_counters/register_ui_counters_collector.ts index c2e17c24de488..0eb9d291337a7 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ui_counters/register_ui_counters_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ui_counters/register_ui_counters_collector.ts @@ -57,29 +57,29 @@ export function transformRawUsageCounterObject( } export async function fetchUiCounters({ soClient }: CollectorFetchContext) { - const { saved_objects: rawUsageCounters } = - await soClient.find({ - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - fields: ['count', 'counterName', 'counterType', 'domainId'], - filter: `${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.attributes.domainId: uiCounter`, - perPage: 10000, - }); + const finder = soClient.createPointInTimeFinder({ + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + fields: ['count', 'counterName', 'counterType', 'domainId'], + filter: `${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.attributes.domainId: uiCounter`, + perPage: 1000, + }); - return { - dailyEvents: Object.values( - rawUsageCounters.reduce((acc, raw) => { - try { - const event = transformRawUsageCounterObject(raw); - if (event) { - acc[raw.id] = event; - } - } catch (_) { - // swallow error; allows sending successfully transformed objects. + const dailyEvents: UiCounterEvent[] = []; + + for await (const { saved_objects: rawUsageCounters } of finder.find()) { + rawUsageCounters.forEach((raw) => { + try { + const event = transformRawUsageCounterObject(raw); + if (event) { + dailyEvents.push(event); } - return acc; - }, {} as Record) - ), - }; + } catch (_) { + // swallow error; allows sending successfully transformed objects. + } + }); + } + + return { dailyEvents }; } export function registerUiCountersUsageCollector(usageCollection: UsageCollectionSetup) { diff --git a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts index 3d49bca3bb2a4..833e537b92ddf 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -52,26 +52,28 @@ export function registerUiMetricUsageCollector( return; } - const { saved_objects: rawUiMetrics } = await savedObjectsClient.find({ + const finder = savedObjectsClient.createPointInTimeFinder({ type: 'ui-metric', fields: ['count'], - perPage: 10000, + perPage: 1000, }); - const uiMetricsByAppName = rawUiMetrics.reduce((accum, rawUiMetric) => { - const { - id, - attributes: { count }, - } = rawUiMetric; + const uiMetricsByAppName: UIMetricUsage = {}; - const [appName, ...metricType] = id.split(':'); + for await (const { saved_objects: rawUiMetrics } of finder.find()) { + rawUiMetrics.forEach((rawUiMetric) => { + const { + id, + attributes: { count }, + } = rawUiMetric; - const pair = { key: metricType.join(':'), value: count }; - return { - ...accum, - [appName]: [...(accum[appName] || []), pair], - }; - }, {} as UIMetricUsage); + const [appName, ...metricType] = id.split(':'); + + const pair = { key: metricType.join(':'), value: count }; + + uiMetricsByAppName[appName] = [...(uiMetricsByAppName[appName] || []), pair]; + }); + } return uiMetricsByAppName; }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/register_usage_counters_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/register_usage_counters_collector.ts index 3899dac73f939..a46782bf8563a 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/usage_counters/register_usage_counters_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/usage_counters/register_usage_counters_collector.ts @@ -86,27 +86,29 @@ export function registerUsageCountersUsageCollector(usageCollection: UsageCollec }, }, fetch: async ({ soClient }: CollectorFetchContext) => { - const { saved_objects: rawUsageCounters } = - await soClient.find({ - type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, - fields: ['count', 'counterName', 'counterType', 'domainId'], - filter: `NOT ${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.attributes.domainId: uiCounter`, - perPage: 10000, - }); + const finder = soClient.createPointInTimeFinder({ + type: USAGE_COUNTERS_SAVED_OBJECT_TYPE, + fields: ['count', 'counterName', 'counterType', 'domainId'], + filter: `NOT ${USAGE_COUNTERS_SAVED_OBJECT_TYPE}.attributes.domainId: uiCounter`, + perPage: 1000, + }); + + const dailyEvents: UsageCounterEvent[] = []; - return { - dailyEvents: rawUsageCounters.reduce((acc, rawUsageCounter) => { + for await (const { saved_objects: rawUsageCounters } of finder.find()) { + rawUsageCounters.forEach((rawUsageCounter) => { try { const event = transformRawCounter(rawUsageCounter); if (event) { - acc.push(event); + dailyEvents.push(event); } } catch (_) { // swallow error; allows sending successfully transformed objects. } - return acc; - }, [] as UsageCounterEvent[]), - }; + }); + } + + return { dailyEvents }; }, isReady: () => true, }); diff --git a/src/plugins/presentation_util/public/__stories__/fixtures/flights.ts b/src/plugins/presentation_util/public/__stories__/fixtures/flights.ts index c574d18b8de31..17cedff90ad23 100644 --- a/src/plugins/presentation_util/public/__stories__/fixtures/flights.ts +++ b/src/plugins/presentation_util/public/__stories__/fixtures/flights.ts @@ -8,6 +8,11 @@ import { map, uniq } from 'lodash'; import { DataView, DataViewField, IIndexPatternFieldList } from '@kbn/data-views-plugin/public'; +import { + FieldFormatsGetConfigFn, + NumberFormat, + StringFormat, +} from '@kbn/field-formats-plugin/common'; import { flights } from './flights_data'; export type Flight = typeof flights[number]; @@ -53,6 +58,8 @@ const numberFields = [ 'FlightTimeMin', ]; +const getConfig = (() => {}) as FieldFormatsGetConfigFn; + export const flightFieldByName: { [key: string]: DataViewField } = {}; flightFieldNames.forEach( (flightFieldName) => @@ -72,6 +79,10 @@ export const storybookFlightsDataView: DataView = { title: 'demo data flights', fields: flightFields as unknown as IIndexPatternFieldList, getFieldByName: (name: string) => flightFieldByName[name], + getFormatterForField: (field: DataViewField) => { + if (numberFields.includes(field.name)) return new NumberFormat({}, getConfig); + return new StringFormat({}, getConfig); + }, } as unknown as DataView; export const getFlightOptions = (field: string) => uniq(map(flights, field)).sort(); diff --git a/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts b/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts index 4412eeb78c428..98531c2fec879 100644 --- a/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts +++ b/src/plugins/usage_collection/server/routes/integration_tests/stats.test.ts @@ -8,7 +8,12 @@ import { BehaviorSubject } from 'rxjs'; -import { MetricsServiceSetup, ServiceStatus, ServiceStatusLevels } from '@kbn/core/server'; +import { + MetricsServiceSetup, + RequestHandlerContext, + ServiceStatus, + ServiceStatusLevels, +} from '@kbn/core/server'; import { contextServiceMock, loggingSystemMock, @@ -42,7 +47,7 @@ describe('/api/stats', () => { }); metrics = metricsServiceMock.createSetupContract(); - const router = httpSetup.createRouter(''); + const router = httpSetup.createRouter(''); registerStatsRoute({ router, collectorSet: new CollectorSet({ diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/annotations/buckets.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/annotations/buckets.ts index 1f17b1ecdf2c7..9b353b36e7428 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/annotations/buckets.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/annotations/buckets.ts @@ -7,7 +7,7 @@ */ import { get, isEmpty } from 'lodash'; -import type { SearchResponse } from '@kbn/core/server'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { overwrite } from '../../helpers'; import type { Annotation } from '../../../../../common/types'; diff --git a/test/api_integration/apis/home/sample_data.ts b/test/api_integration/apis/home/sample_data.ts index 6636a490118b4..da33f5322e8b7 100644 --- a/test/api_integration/apis/home/sample_data.ts +++ b/test/api_integration/apis/home/sample_data.ts @@ -21,6 +21,10 @@ export default function ({ getService }: FtrProviderContext) { const FLIGHTS_CANVAS_APPLINK_PATH = '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0'; // includes default ID of the flights canvas applink path + const includesPathInAppLinks = (appLinks: Array<{ path: string }>, path: string): boolean => { + return appLinks.some((item) => item.path === path); + }; + describe('sample data apis', () => { before(async () => { await esArchiver.emptyKibanaIndex(); @@ -41,7 +45,9 @@ export default function ({ getService }: FtrProviderContext) { // Check and make sure the sample dataset reflects the default object IDs, because no sample data objects exist. // Instead of checking each object ID, we check the dashboard and canvas app link as representatives. expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); - expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH); + expect(includesPathInAppLinks(flightsData.appLinks, FLIGHTS_CANVAS_APPLINK_PATH)).to.be( + true + ); }); }); @@ -109,11 +115,15 @@ export default function ({ getService }: FtrProviderContext) { // Instead of checking each object ID, we check the dashboard and canvas app link as representatives. if (space === 'default') { expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); - expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH); + expect(includesPathInAppLinks(flightsData.appLinks, FLIGHTS_CANVAS_APPLINK_PATH)).to.be( + true + ); } else { // the sample data objects installed in the 'other' space had their IDs regenerated upon import expect(flightsData.overviewDashboard).not.to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); - expect(flightsData.appLinks[0].path).not.to.be(FLIGHTS_CANVAS_APPLINK_PATH); + expect(includesPathInAppLinks(flightsData.appLinks, FLIGHTS_CANVAS_APPLINK_PATH)).to.be( + false + ); } }); }); @@ -145,7 +155,9 @@ export default function ({ getService }: FtrProviderContext) { // Check and make sure the sample dataset reflects the default object IDs, because no sample data objects exist. // Instead of checking each object ID, we check the dashboard and canvas app link as representatives. expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID); - expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH); + expect(includesPathInAppLinks(flightsData.appLinks, FLIGHTS_CANVAS_APPLINK_PATH)).to.be( + true + ); }); }); } diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index 126788c3312d2..a79f46280dd66 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -154,5 +154,36 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.console.hasSuccessBadge()).to.be(true); }); }); + + describe('with folded/unfolded lines in request body', () => { + const enterRequestWithBody = async () => { + await PageObjects.console.enterRequest(); + await PageObjects.console.pressEnter(); + await PageObjects.console.enterText('{\n\t\t"_source": []'); + }; + + it('should save the state of folding/unfolding when navigating back to Console', async () => { + await PageObjects.console.clearTextArea(); + await enterRequestWithBody(); + await PageObjects.console.clickFoldWidget(); + await PageObjects.common.navigateToApp('home'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.navigateToApp('console'); + expect(await PageObjects.console.hasFolds()).to.be(true); + }); + + it('should save the state of folding/unfolding when the page reloads', async () => { + // blocks the close help button for several seconds so just retry until we can click it. + await retry.try(async () => { + await PageObjects.console.collapseHelp(); + }); + await PageObjects.console.clearTextArea(); + await enterRequestWithBody(); + await PageObjects.console.clickFoldWidget(); + await browser.refresh(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.console.hasFolds()).to.be(true); + }); + }); }); } diff --git a/test/functional/apps/discover/_discover_accessibility.ts b/test/functional/apps/discover/_discover_accessibility.ts index 1f84efe125b75..4b2426681d3f2 100644 --- a/test/functional/apps/discover/_discover_accessibility.ts +++ b/test/functional/apps/discover/_discover_accessibility.ts @@ -54,8 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await hasFocus('discover-dataView-switch-link')).to.be(true); }); - // FLAKY: https://github.com/elastic/kibana/issues/135305 - describe.skip('top nav menu buttons', () => { + describe('top nav menu buttons', () => { const focusAndPressButton = async (buttonTestSubject: string | WebElementWrapper) => { const button = typeof buttonTestSubject === 'string' @@ -87,11 +86,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await focusAndPressButton('discoverAlertsButton'); expect(await hasFocus('discoverAlertsButton')).to.be(false); await focusAndPressButton('discoverCreateAlertButton'); - await retry.waitFor( - 'Create Rule flyout is visible', - async () => await testSubjects.exists('addRuleFlyoutTitle') - ); - expect(await hasFocus('discoverCreateAlertButton')).to.be(false); + expect(await testSubjects.exists('addRuleFlyoutTitle')).to.be(true); await retry.try(async () => { await browser.pressKeys(browser.keys.ESCAPE); // A bug exists with the create rule flyout where sometimes the confirm modal diff --git a/test/functional/apps/discover/_doc_accessibility.ts b/test/functional/apps/discover/_doc_accessibility.ts index 7dbc8eb1948b5..446c134727e26 100644 --- a/test/functional/apps/discover/_doc_accessibility.ts +++ b/test/functional/apps/discover/_doc_accessibility.ts @@ -52,8 +52,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should give focus to the first tab link when Tab is pressed', async () => { - await browser.pressKeys(browser.keys.TAB); const tableTab = await testSubjects.find('docViewerTab-0'); + await browser.pressKeys(browser.keys.TAB); const activeElement = await find.activeElement(); expect(await tableTab.getAttribute('data-test-subj')).to.eql( await activeElement.getAttribute('data-test-subj') diff --git a/test/functional/apps/management/_handle_alias.ts b/test/functional/apps/management/_handle_alias.ts index 04496bf9ed758..82544a7be55f8 100644 --- a/test/functional/apps/management/_handle_alias.ts +++ b/test/functional/apps/management/_handle_alias.ts @@ -56,23 +56,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.settings.createIndexPattern('alias2*', 'date'); }); - it('should be able to discover and verify no of hits for alias2', async function () { - const expectedHitCount = '5'; - const fromTime = 'Nov 12, 2016 @ 05:00:00.000'; - const toTime = 'Nov 19, 2016 @ 05:00:00.000'; + describe('discover verify hits', async () => { + before(async () => { + const from = 'Nov 12, 2016 @ 05:00:00.000'; + const to = 'Nov 19, 2016 @ 05:00:00.000'; + await PageObjects.common.setTime({ from, to }); + }); - await PageObjects.common.navigateToApp('discover'); - await PageObjects.discover.selectIndexPattern('alias2*'); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + it('should be able to discover and verify no of hits for alias2', async function () { + const expectedHitCount = '5'; + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('alias2*'); - await retry.try(async function () { - expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount); + await retry.waitForWithTimeout('expected hit count to be 5', 30000, async () => { + return (await PageObjects.discover.getHitCount()) === expectedHitCount; + }); }); - }); - after(async () => { - await security.testUser.restoreDefaults(); - await esArchiver.unload('test/functional/fixtures/es_archiver/alias'); + after(async () => { + await PageObjects.common.unsetTime(); + await security.testUser.restoreDefaults(); + await esArchiver.unload('test/functional/fixtures/es_archiver/alias'); + }); }); }); } diff --git a/test/functional/apps/management/_scripted_fields.ts b/test/functional/apps/management/_scripted_fields.ts index cefaa5b295369..04a3c104c1bc6 100644 --- a/test/functional/apps/management/_scripted_fields.ts +++ b/test/functional/apps/management/_scripted_fields.ts @@ -144,79 +144,94 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it('should see scripted field value in Discover', async function () { - const fromTime = 'Sep 17, 2015 @ 06:31:44.000'; - const toTime = 'Sep 18, 2015 @ 18:31:44.000'; - await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); - await retry.try(async function () { - await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName); + describe('discover scripted field', async () => { + before(async () => { + const from = 'Sep 17, 2015 @ 06:31:44.000'; + const to = 'Sep 18, 2015 @ 18:31:44.000'; + await PageObjects.common.setTime({ from, to }); }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = (await dataGrid.getRowsText())[0]; - expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.91618'); - }); - }); + it('should see scripted field value in Discover', async function () { + await PageObjects.common.navigateToApp('discover'); - // add a test to sort numeric scripted field - it('should sort scripted field value in Discover', async function () { - await clickSort(scriptedPainlessFieldName, 1); - await PageObjects.common.sleep(500); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); + await retry.try(async function () { + await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); - // after the first click on the scripted field, it becomes secondary sort after time. - // click on the timestamp twice to make it be the secondary sort key. - await clickSort('@timestamp', 1); - await clickSort('@timestamp', 0); + await retry.try(async function () { + const rowData = (await dataGrid.getRowsText())[0]; + expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.91618'); + }); + }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = (await dataGrid.getRowsText())[0]; - expect(rowData).to.be('Sep 17, 2015 @ 10:53:14.181-1'); + // add a test to sort numeric scripted field + it('should sort scripted field value in Discover', async function () { + await clickSort(scriptedPainlessFieldName, 1); + await PageObjects.common.sleep(500); + + // after the first click on the scripted field, it becomes secondary sort after time. + // click on the timestamp twice to make it be the secondary sort key. + await clickSort('@timestamp', 1); + await clickSort('@timestamp', 0); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = (await dataGrid.getRowsText())[0]; + expect(rowData).to.be('Sep 17, 2015 @ 10:53:14.181-1'); + }); + + await clickSort(scriptedPainlessFieldName, 2); + // after the first click on the scripted field, it becomes primary sort after time. + // click on the scripted field twice then, makes it be the secondary sort key. + await clickSort(scriptedPainlessFieldName, 2); + await clickSort(scriptedPainlessFieldName, 2); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await retry.try(async function () { + const rowData = (await dataGrid.getRowsText())[0]; + expect(rowData).to.be('Sep 17, 2015 @ 06:32:29.47920'); + }); }); - await clickSort(scriptedPainlessFieldName, 2); - // after the first click on the scripted field, it becomes primary sort after time. - // click on the scripted field twice then, makes it be the secondary sort key. - await clickSort(scriptedPainlessFieldName, 2); - await clickSort(scriptedPainlessFieldName, 2); + it('should filter by scripted field value in Discover', async function () { + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); + await log.debug('filter by the first value (14) in the expanded scripted field list'); + await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName, '14'); + await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - const rowData = (await dataGrid.getRowsText())[0]; - expect(rowData).to.be('Sep 17, 2015 @ 06:32:29.47920'); + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be('31'); + }); }); - }); - - it('should filter by scripted field value in Discover', async function () { - await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); - await log.debug('filter by the first value (14) in the expanded scripted field list'); - await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName, '14'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await retry.try(async function () { - expect(await PageObjects.discover.getHitCount()).to.be('31'); + it('should visualize scripted field in vertical bar chart', async function () { + await filterBar.removeAllFilters(); + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); + await PageObjects.header.waitUntilLoadingHasFinished(); + // verify Lens opens a visualization + expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( + '@timestamp', + 'Median of ram_Pain1' + ); }); }); - it('should visualize scripted field in vertical bar chart', async function () { - await filterBar.removeAllFilters(); - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); - await PageObjects.header.waitUntilLoadingHasFinished(); - // verify Lens opens a visualization - expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( - '@timestamp', - 'Median of ram_Pain1' - ); + after(async () => { + await PageObjects.common.unsetTime(); }); }); describe('creating and using Painless string scripted fields', function describeIndexTests() { const scriptedPainlessFieldName2 = 'painString'; + before(async () => { + const from = 'Sep 17, 2015 @ 06:31:44.000'; + const to = 'Sep 18, 2015 @ 18:31:44.000'; + await PageObjects.common.setTime({ from, to }); + }); + it('should create scripted field', async function () { await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaIndexPatterns(); @@ -240,10 +255,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should see scripted field value in Discover', async function () { - const fromTime = 'Sep 17, 2015 @ 06:31:44.000'; - const toTime = 'Sep 18, 2015 @ 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await retry.try(async function () { @@ -306,11 +318,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Top 5 values of painString' ); }); + + after(async () => { + await PageObjects.common.unsetTime(); + }); }); describe('creating and using Painless boolean scripted fields', function describeIndexTests() { const scriptedPainlessFieldName2 = 'painBool'; + before(async () => { + const from = 'Sep 17, 2015 @ 06:31:44.000'; + const to = 'Sep 18, 2015 @ 18:31:44.000'; + await PageObjects.common.setTime({ from, to }); + }); + it('should create scripted field', async function () { await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaIndexPatterns(); @@ -334,10 +356,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should see scripted field value in Discover', async function () { - const fromTime = 'Sep 17, 2015 @ 06:31:44.000'; - const toTime = 'Sep 18, 2015 @ 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await retry.try(async function () { @@ -393,11 +412,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Top 5 values of painBool' ); }); + + after(async () => { + await PageObjects.common.unsetTime(); + }); }); describe('creating and using Painless date scripted fields', function describeIndexTests() { const scriptedPainlessFieldName2 = 'painDate'; + before(async () => { + const from = 'Sep 17, 2015 @ 19:22:00.000'; + const to = 'Sep 18, 2015 @ 07:00:00.000'; + await PageObjects.common.setTime({ from, to }); + }); + it('should create scripted field', async function () { await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaIndexPatterns(); @@ -421,10 +450,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should see scripted field value in Discover', async function () { - const fromTime = 'Sep 17, 2015 @ 19:22:00.000'; - const toTime = 'Sep 18, 2015 @ 07:00:00.000'; await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await retry.try(async function () { @@ -482,5 +508,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain('painDate'); }); }); + + after(async () => { + await PageObjects.common.unsetTime(); + }); }); } diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 8f8308b4eef5a..17ab4e1b170f7 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -206,4 +206,19 @@ export class ConsolePageObject extends FtrService { return false; } } + + public async clickFoldWidget() { + const widget = await this.find.byCssSelector('.ace_fold-widget'); + await widget.click(); + } + + public async hasFolds() { + try { + const requestEditor = await this.getRequestEditor(); + const folds = await requestEditor.findAllByCssSelector('.ace_fold'); + return folds.length > 0; + } catch (e) { + return false; + } + } } diff --git a/test/new_visualize_flow/fixtures/es_archiver/kibana/data.json b/test/new_visualize_flow/fixtures/es_archiver/kibana/data.json index 14d67f9bfbc34..fe554f21db45e 100644 --- a/test/new_visualize_flow/fixtures/es_archiver/kibana/data.json +++ b/test/new_visualize_flow/fixtures/es_archiver/kibana/data.json @@ -819,926 +819,6 @@ } } -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:84908bb0-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.10425, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:04:37.610Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:04:37.611Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:84908bb1-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "home", - "minutesOnScreen": 0.5708666666666666, - "numberOfClicks": 1, - "timestamp": "2020-05-31T11:04:37.610Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:04:37.611Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:951d7420-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 1.3920166666666667, - "numberOfClicks": 39, - "timestamp": "2020-05-31T11:05:05.378Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:05:05.378Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:a79fc3f0-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.45816666666666667, - "numberOfClicks": 8, - "timestamp": "2020-05-31T11:05:36.431Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:05:36.431Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:bc30cf80-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.5057333333333333, - "numberOfClicks": 14, - "timestamp": "2020-05-31T11:06:10.935Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:06:10.936Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:df4781d0-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.3740833333333333, - "numberOfClicks": 1, - "timestamp": "2020-05-31T11:07:09.804Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:07:09.805Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:dffd3d40-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.02105, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:07:10.996Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:07:10.996Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:e05dd3d0-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.010416666666666666, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:07:11.629Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:07:11.629Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:cfc85fe0-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.5466333333333333, - "numberOfClicks": 16, - "timestamp": "2020-05-31T11:06:43.806Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:06:43.806Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:00710a20-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.8720333333333333, - "numberOfClicks": 14, - "timestamp": "2020-05-31T11:08:05.442Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:08:05.442Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:e1454da0-a32e-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.01815, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:07:13.146Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:07:13.146Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:0eb5d750-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.33895, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:08:29.380Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:08:29.381Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:2e09dc50-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.08756666666666667, - "numberOfClicks": 3, - "timestamp": "2020-05-31T11:09:21.941Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:21.941Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:25916f20-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.5207166666666667, - "numberOfClicks": 5, - "timestamp": "2020-05-31T11:09:07.730Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:07.730Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:1e9c4690-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.38158333333333333, - "numberOfClicks": 17, - "timestamp": "2020-05-31T11:08:56.057Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:08:56.057Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:1f41ae50-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.0183, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:08:57.141Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:08:57.141Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:26923d50-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.0285, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:09.413Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:09.413Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:1024f7b0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.0401, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:08:31.787Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:08:31.787Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:10f7d810-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.012, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:08:33.169Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:08:33.169Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:34f010c0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.28990000000000005, - "numberOfClicks": 4, - "timestamp": "2020-05-31T11:09:33.515Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:33.516Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:35665230-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.012216666666666667, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:34.291Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:34.291Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:404deaa0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.24620000000000003, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:52.586Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:52.586Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:415ac6c0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.015816666666666666, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:54.348Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:54.348Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:40c898e0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.013533333333333333, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:53.390Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:53.390Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:434a13a0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.011566666666666666, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:57.594Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:57.594Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:42de5980-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.042, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:09:56.888Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:09:56.888Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:ffde98b0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 2.983433333333333, - "numberOfClicks": 31, - "timestamp": "2020-05-31T11:15:13.979Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:15:13.979Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:46ca9260-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.3946, - "numberOfClicks": 4, - "timestamp": "2020-05-31T11:17:12.966Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:17:12.966Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:47072630-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.006116666666666667, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:17:13.363Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:17:13.363Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:3805bfc0-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.19001666666666667, - "numberOfClicks": 2, - "timestamp": "2020-05-31T11:16:48.188Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:16:48.188Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:38af4630-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.018766666666666668, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:16:49.299Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:16:49.299Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:3136e3e0-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.012466666666666666, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:16:36.766Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:16:36.766Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:fd3cebc0-a32f-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.5283, - "numberOfClicks": 17, - "timestamp": "2020-05-31T11:15:09.564Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:15:09.564Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6463c800-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.28708333333333336, - "numberOfClicks": 10, - "timestamp": "2020-05-31T11:18:02.624Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:02.624Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:57b7c340-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.4411833333333333, - "numberOfClicks": 11, - "timestamp": "2020-05-31T11:17:41.364Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:17:41.364Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:67dbd7c0-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 2.4508833333333335, - "numberOfClicks": 5, - "timestamp": "2020-05-31T11:18:08.444Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:08.444Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6bf41f20-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 2.5884833333333335, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:18:15.314Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:15.314Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6c5ccc00-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.011383333333333334, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:18:16.000Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:16.000Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6ce7f500-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.015066666666666667, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:18:16.912Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:16.912Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:5a178530-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.067, - "numberOfClicks": 3, - "timestamp": "2020-05-31T11:17:45.347Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:17:45.347Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:d10c66b0-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.030199999999999998, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:21:04.923Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:21:04.923Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:cd5aa950-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 2.2735666666666665, - "numberOfClicks": 21, - "timestamp": "2020-05-31T11:20:58.724Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:20:58.725Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:b2dee050-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.08906666666666667, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:20:14.293Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:20:14.293Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:d194a980-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.018183333333333333, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:21:05.816Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:21:05.816Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:72b406e0-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.14396666666666666, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:18:26.638Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:26.638Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6d8e6e30-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.0179, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:18:18.003Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:18:18.003Z" - } - } -} - { "type": "doc", "value": { @@ -3969,66 +3049,6 @@ } } -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:d9ce6000-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.2313, - "numberOfClicks": 0, - "timestamp": "2020-05-31T11:21:19.616Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:21:19.616Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:dc1d0af0-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 0.06526666666666667, - "numberOfClicks": 3, - "timestamp": "2020-05-31T11:21:23.487Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:21:23.487Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:f22c7920-a330-11ea-88c2-d56dd2b14bd7", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 0.5732333333333333, - "numberOfClicks": 13, - "timestamp": "2020-05-31T11:22:00.498Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-05-31T11:22:00.498Z" - } - } -} - { "type": "doc", "value": { @@ -4114,26 +3134,6 @@ } } -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:b2a73c00-a645-11ea-b4c2-47e842e5fce5", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 1.2560333333333333, - "numberOfClicks": 19, - "timestamp": "2020-06-04T09:28:06.848Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-06-04T09:28:06.848Z" - } - } -} - { "type": "doc", "value": { @@ -4151,46 +3151,6 @@ } } -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6847ed80-a645-11ea-b4c2-47e842e5fce5", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 2.291733333333333, - "numberOfClicks": 16, - "timestamp": "2020-06-04T09:26:02.071Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-06-04T09:26:02.072Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:7b331140-a645-11ea-b4c2-47e842e5fce5", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 1.2560333333333333, - "numberOfClicks": 19, - "timestamp": "2020-06-04T09:26:33.812Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-06-04T09:26:33.812Z" - } - } -} - { "type": "doc", "value": { @@ -4206,24 +3166,4 @@ "updated_at": "2020-06-04T09:28:06.848Z" } } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:6f47f610-a646-11ea-b4c2-47e842e5fce5", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "dashboards", - "minutesOnScreen": 6.77335, - "numberOfClicks": 5, - "timestamp": "2020-06-04T09:33:23.313Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-06-04T09:33:23.313Z" - } - } } \ No newline at end of file diff --git a/tsconfig.types.json b/tsconfig.types.json index 0217a2cde12bb..470745f52d5c3 100644 --- a/tsconfig.types.json +++ b/tsconfig.types.json @@ -5,6 +5,7 @@ "outDir": "./target/types", "stripInternal": false, "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "declarationMap": true, "rootDir": "./src" diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 47890ec013046..c15d92ff57985 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -38,7 +38,7 @@ "xpack.main": "legacy/plugins/xpack_main", "xpack.maps": ["plugins/maps"], "xpack.aiops": [ - "packages/ml/aiops_utils", + "packages/ml/aiops_components", "plugins/aiops" ], "xpack.ml": ["plugins/ml"], diff --git a/x-pack/packages/ml/agg_utils/BUILD.bazel b/x-pack/packages/ml/agg_utils/BUILD.bazel index 0d59aca092fd5..ee8343f8d45e7 100644 --- a/x-pack/packages/ml/agg_utils/BUILD.bazel +++ b/x-pack/packages/ml/agg_utils/BUILD.bazel @@ -84,6 +84,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/x-pack/packages/ml/agg_utils/tsconfig.json b/x-pack/packages/ml/agg_utils/tsconfig.json index b74cfcda5ee73..58e77e186f730 100644 --- a/x-pack/packages/ml/agg_utils/tsconfig.json +++ b/x-pack/packages/ml/agg_utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/x-pack/packages/ml/aiops_components/BUILD.bazel b/x-pack/packages/ml/aiops_components/BUILD.bazel new file mode 100644 index 0000000000000..f08ccff0d2893 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/BUILD.bazel @@ -0,0 +1,145 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "aiops_components" +PKG_REQUIRE_NAME = "@kbn/aiops-components" + +SOURCE_FILES = glob( + [ + "src/**/*.scss", + "src/**/*.ts", + "src/**/*.tsx", + ], + exclude = [ + "**/*.test.*", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +# In this array place runtime dependencies, including other packages and NPM packages +# which must be available for this code to run. +# +# To reference other packages use: +# "//repo/relative/path/to/package" +# eg. "//packages/kbn-utils" +# +# To reference a NPM package use: +# "@npm//name-of-package" +# eg. "@npm//lodash" +RUNTIME_DEPS = [ + "@npm//d3-brush", + "@npm//d3-scale", + "@npm//d3-selection", + "@npm//d3-transition", + "@npm//react", + "@npm//@elastic/charts", + "@npm//@elastic/eui", + "//packages/kbn-i18n-react", + "//x-pack/packages/ml/aiops_utils", +] + +# In this array place dependencies necessary to build the types, which will include the +# :npm_module_types target of other packages and packages from NPM, including @types/* +# packages. +# +# To reference the types for another package use: +# "//repo/relative/path/to/package:npm_module_types" +# eg. "//packages/kbn-utils:npm_module_types" +# +# References to NPM packages work the same as RUNTIME_DEPS +TYPES_DEPS = [ + "@npm//@types/d3-brush", + "@npm//@types/d3-scale", + "@npm//@types/d3-selection", + "@npm//@types/d3-transition", + "@npm//@types/node", + "@npm//@types/jest", + "@npm//@types/react", + "@npm//@elastic/charts", + "@npm//@elastic/eui", + "//packages/kbn-i18n-react:npm_module_types", + "//x-pack/packages/ml/aiops_utils:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +jsts_transpiler( + name = "target_web", + srcs = SRCS, + build_pkg_name = package_name(), + web = True, + additional_args = [ + "--copy-files", + "--quiet" + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + emit_declaration_only = True, + out_dir = "target_types", + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node", ":target_web"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/x-pack/packages/ml/aiops_components/README.md b/x-pack/packages/ml/aiops_components/README.md new file mode 100644 index 0000000000000..36b36805d2872 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/README.md @@ -0,0 +1,5 @@ +# @kbn/aiops-components + +React components for AIOps related efforts. + +https://docs.elastic.dev/kibana-dev-docs/api/kbn-aiops-components diff --git a/x-pack/packages/ml/aiops_components/jest.config.js b/x-pack/packages/ml/aiops_components/jest.config.js new file mode 100644 index 0000000000000..cadc9733723e9 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/ml/aiops_components'], +}; diff --git a/x-pack/packages/ml/aiops_components/package.json b/x-pack/packages/ml/aiops_components/package.json new file mode 100644 index 0000000000000..f3cb901271998 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/package.json @@ -0,0 +1,11 @@ +{ + "name": "@kbn/aiops-components", + "description": "React components for AIOps related efforts.", + "author": "Machine Learning UI", + "homepage": "https://docs.elastic.dev/kibana-dev-docs/api/kbn-aiops-components", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "browser": "./target_web/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.scss b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.scss new file mode 100644 index 0000000000000..a97dec29ecd62 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.scss @@ -0,0 +1,11 @@ +.aiops-dual-brush { + .handle { + fill: $euiColorDarkShade; + } + + .brush .selection { + stroke: none; + fill: $euiColorDarkShade !important; + opacity: .5 !important; + } +} diff --git a/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx new file mode 100644 index 0000000000000..bb44f4ac16a89 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush.tsx @@ -0,0 +1,264 @@ +/* + * 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, { useEffect, useRef } from 'react'; + +import * as d3Brush from 'd3-brush'; +import * as d3Scale from 'd3-scale'; +import * as d3Selection from 'd3-selection'; +import * as d3Transition from 'd3-transition'; + +import type { WindowParameters } from '@kbn/aiops-utils'; + +import './dual_brush.scss'; + +const { brush, brushSelection, brushX } = d3Brush; +const { scaleLinear } = d3Scale; +const { select: d3Select } = d3Selection; +// Import fix to apply correct types for the use of d3.select(this).transition() +d3Select.prototype.transition = d3Transition.transition; + +const d3 = { + brush, + brushSelection, + brushX, + scaleLinear, + select: d3Select, + transition: d3Transition, +}; + +const isBrushXSelection = (arg: unknown): arg is [number, number] => { + return ( + Array.isArray(arg) && + arg.length === 2 && + typeof arg[0] === 'number' && + typeof arg[1] === 'number' + ); +}; + +interface DualBrush { + id: string; + brush: d3Brush.BrushBehavior; + start: number; + end: number; +} + +const BRUSH_HEIGHT = 20; +const BRUSH_MARGIN = 4; +const BRUSH_HANDLE_SIZE = 4; +const BRUSH_HANDLE_ROUNDED_CORNER = 2; + +interface DualBrushProps { + windowParameters: WindowParameters; + min: number; + max: number; + onChange?: (windowParameters: WindowParameters) => void; + marginLeft: number; + width: number; +} + +export function DualBrush({ + windowParameters, + min, + max, + onChange, + marginLeft, + width, +}: DualBrushProps) { + const d3BrushContainer = useRef(null); + const brushes = useRef([]); + const widthRef = useRef(width); + + const { baselineMin, baselineMax, deviationMin, deviationMax } = windowParameters; + + useEffect(() => { + if (d3BrushContainer.current && width > 0) { + const gBrushes = d3.select(d3BrushContainer.current); + + function newBrush(id: string, start: number, end: number) { + brushes.current.push({ + id, + brush: d3.brushX().handleSize(BRUSH_HANDLE_SIZE).on('end', brushend), + start, + end, + }); + + function brushend(this: d3Selection.BaseType) { + const currentWidth = widthRef.current; + + const x = d3.scaleLinear().domain([min, max]).rangeRound([0, currentWidth]); + + const px2ts = (px: number) => Math.round(x.invert(px)); + const xMin = x(min) ?? 0; + const xMax = x(max) ?? 0; + const minExtentPx = Math.round((xMax - xMin) / 100); + + const baselineBrush = d3.select('#brush-baseline'); + const baselineSelection = d3.brushSelection(baselineBrush.node() as SVGGElement); + + const deviationBrush = d3.select('#brush-deviation'); + const deviationSelection = d3.brushSelection(deviationBrush.node() as SVGGElement); + + if (!isBrushXSelection(deviationSelection) || !isBrushXSelection(baselineSelection)) { + return; + } + + const baselineOverlay = baselineBrush.selectAll('.overlay'); + const deviationOverlay = deviationBrush.selectAll('.overlay'); + + let baselineWidth; + let deviationWidth; + baselineOverlay.each((d, i, n) => { + baselineWidth = d3.select(n[i]).attr('width'); + }); + deviationOverlay.each((d, i, n) => { + deviationWidth = d3.select(n[i]).attr('width'); + }); + + if (baselineWidth !== deviationWidth) { + return; + } + + const newWindowParameters = { + baselineMin: px2ts(baselineSelection[0]), + baselineMax: px2ts(baselineSelection[1]), + deviationMin: px2ts(deviationSelection[0]), + deviationMax: px2ts(deviationSelection[1]), + }; + + if ( + id === 'deviation' && + deviationSelection && + baselineSelection && + deviationSelection[0] - minExtentPx < baselineSelection[1] + ) { + const newDeviationMin = baselineSelection[1] + minExtentPx; + const newDeviationMax = Math.max(deviationSelection[1], newDeviationMin + minExtentPx); + + newWindowParameters.deviationMin = px2ts(newDeviationMin); + newWindowParameters.deviationMax = px2ts(newDeviationMax); + + d3.select(this) + .transition() + .duration(200) + // @ts-expect-error call doesn't allow the brush move function + .call(brushes.current[1].brush.move, [newDeviationMin, newDeviationMax]); + } else if ( + id === 'baseline' && + deviationSelection && + baselineSelection && + deviationSelection[0] < baselineSelection[1] + minExtentPx + ) { + const newBaselineMax = deviationSelection[0] - minExtentPx; + const newBaselineMin = Math.min(baselineSelection[0], newBaselineMax - minExtentPx); + + newWindowParameters.baselineMin = px2ts(newBaselineMin); + newWindowParameters.baselineMax = px2ts(newBaselineMax); + + d3.select(this) + .transition() + .duration(200) + // @ts-expect-error call doesn't allow the brush move function + .call(brushes.current[0].brush.move, [newBaselineMin, newBaselineMax]); + } + + brushes.current[0].start = newWindowParameters.baselineMin; + brushes.current[0].end = newWindowParameters.baselineMax; + brushes.current[1].start = newWindowParameters.deviationMin; + brushes.current[1].end = newWindowParameters.deviationMax; + + if (onChange) { + onChange(newWindowParameters); + } + drawBrushes(); + } + } + + function drawBrushes() { + const mlBrushSelection = gBrushes + .selectAll('.brush') + .data(brushes.current, (d) => (d as DualBrush).id); + + // Set up new brushes + mlBrushSelection + .enter() + .insert('g', '.brush') + .attr('class', 'brush') + .attr('id', (b: DualBrush) => { + return 'brush-' + b.id; + }) + .each((brushObject: DualBrush, i, n) => { + const x = d3.scaleLinear().domain([min, max]).rangeRound([0, widthRef.current]); + brushObject.brush(d3.select(n[i])); + const xStart = x(brushObject.start) ?? 0; + const xEnd = x(brushObject.end) ?? 0; + brushObject.brush.move(d3.select(n[i]), [xStart, xEnd]); + }); + + // disable drag-select to reset a brush's selection + mlBrushSelection + .attr('class', 'brush') + .selectAll('.overlay') + .attr('width', width) + .style('pointer-events', 'none'); + + mlBrushSelection + .selectAll('.handle') + .attr('rx', BRUSH_HANDLE_ROUNDED_CORNER) + .attr('ry', BRUSH_HANDLE_ROUNDED_CORNER); + + mlBrushSelection.exit().remove(); + } + + function updateBrushes() { + const mlBrushSelection = gBrushes + .selectAll('.brush') + .data(brushes.current, (d) => (d as DualBrush).id); + + mlBrushSelection.each(function (brushObject, i, n) { + const x = d3.scaleLinear().domain([min, max]).rangeRound([0, widthRef.current]); + brushObject.brush.extent([ + [0, BRUSH_MARGIN], + [width, BRUSH_HEIGHT - BRUSH_MARGIN], + ]); + brushObject.brush(d3.select(n[i] as SVGGElement)); + const xStart = x(brushObject.start) ?? 0; + const xEnd = x(brushObject.end) ?? 0; + brushObject.brush.move(d3.select(n[i] as SVGGElement), [xStart, xEnd]); + }); + } + + if (brushes.current.length !== 2) { + widthRef.current = width; + newBrush('baseline', baselineMin, baselineMax); + newBrush('deviation', deviationMin, deviationMax); + } else { + if (widthRef.current !== width) { + widthRef.current = width; + updateBrushes(); + } + } + + drawBrushes(); + } + }, [min, max, width, baselineMin, baselineMax, deviationMin, deviationMax, onChange]); + + return ( + <> + {width > 0 && ( + + + + )} + + ); +} diff --git a/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush_annotation.tsx b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush_annotation.tsx new file mode 100644 index 0000000000000..54e2c204acfa9 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/src/dual_brush/dual_brush_annotation.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FC } from 'react'; + +import { RectAnnotation } from '@elastic/charts'; +import { useEuiTheme } from '@elastic/eui'; + +interface BrushAnnotationProps { + id: string; + min: number; + max: number; +} + +export const DualBrushAnnotation: FC = ({ id, min, max }) => { + const { euiTheme } = useEuiTheme(); + const { colors } = euiTheme; + + return ( + + ); +}; diff --git a/x-pack/packages/ml/aiops_components/src/dual_brush/index.ts b/x-pack/packages/ml/aiops_components/src/dual_brush/index.ts new file mode 100644 index 0000000000000..c72973a19871f --- /dev/null +++ b/x-pack/packages/ml/aiops_components/src/dual_brush/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 { DualBrushAnnotation } from './dual_brush_annotation'; +export { DualBrush } from './dual_brush'; diff --git a/x-pack/packages/ml/aiops_components/src/index.ts b/x-pack/packages/ml/aiops_components/src/index.ts new file mode 100644 index 0000000000000..d468ffcc4bba5 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/src/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 { DualBrush, DualBrushAnnotation } from './dual_brush'; +export { ProgressControls } from './progress_controls'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_settings/index.ts b/x-pack/packages/ml/aiops_components/src/progress_controls/index.ts similarity index 81% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_settings/index.ts rename to x-pack/packages/ml/aiops_components/src/progress_controls/index.ts index 3f590bfddbe5e..64cbbe174fd11 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_settings/index.ts +++ b/x-pack/packages/ml/aiops_components/src/progress_controls/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { ConnectorSettings } from './connector_settings'; +export { ProgressControls } from './progress_controls'; diff --git a/x-pack/packages/ml/aiops_utils/src/components/progress_controls.tsx b/x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/components/progress_controls.tsx rename to x-pack/packages/ml/aiops_components/src/progress_controls/progress_controls.tsx diff --git a/x-pack/packages/ml/aiops_components/tsconfig.json b/x-pack/packages/ml/aiops_components/tsconfig.json new file mode 100644 index 0000000000000..ebe2e9eb5d0e4 --- /dev/null +++ b/x-pack/packages/ml/aiops_components/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "rootDir": "src", + "stripInternal": false, + "types": [ + "@types/d3-brush", + "@types/d3-scale", + "@types/d3-selection", + "@types/d3-transition", + "jest", + "node", + "react" + ] + }, + "include": [ + "src/**/*" + ] +} diff --git a/x-pack/packages/ml/aiops_utils/BUILD.bazel b/x-pack/packages/ml/aiops_utils/BUILD.bazel index 755c152d62e34..8693044afde2c 100644 --- a/x-pack/packages/ml/aiops_utils/BUILD.bazel +++ b/x-pack/packages/ml/aiops_utils/BUILD.bazel @@ -38,8 +38,6 @@ NPM_MODULE_EXTRA_FILES = [ # eg. "@npm//lodash" RUNTIME_DEPS = [ "@npm//react", - "@npm//@elastic/eui", - "//packages/kbn-i18n-react", "//packages/kbn-logging" ] @@ -56,8 +54,6 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/jest", "@npm//@types/react", - "@npm//@elastic/eui", - "//packages/kbn-i18n-react:npm_module_types", "//packages/kbn-logging:npm_module_types" ] @@ -89,6 +85,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/x-pack/packages/ml/aiops_utils/src/lib/accept_compression.test.ts b/x-pack/packages/ml/aiops_utils/src/accept_compression.test.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/accept_compression.test.ts rename to x-pack/packages/ml/aiops_utils/src/accept_compression.test.ts diff --git a/x-pack/packages/ml/aiops_utils/src/lib/accept_compression.ts b/x-pack/packages/ml/aiops_utils/src/accept_compression.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/accept_compression.ts rename to x-pack/packages/ml/aiops_utils/src/accept_compression.ts diff --git a/x-pack/packages/ml/aiops_utils/src/lib/fetch_stream.ts b/x-pack/packages/ml/aiops_utils/src/fetch_stream.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/fetch_stream.ts rename to x-pack/packages/ml/aiops_utils/src/fetch_stream.ts diff --git a/x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts b/x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/get_window_parameters.ts rename to x-pack/packages/ml/aiops_utils/src/get_window_parameters.ts diff --git a/x-pack/packages/ml/aiops_utils/src/index.ts b/x-pack/packages/ml/aiops_utils/src/index.ts index 1ffbde324470f..a02ecc2d41958 100644 --- a/x-pack/packages/ml/aiops_utils/src/index.ts +++ b/x-pack/packages/ml/aiops_utils/src/index.ts @@ -5,12 +5,11 @@ * 2.0. */ -export { ProgressControls } from './components/progress_controls'; -export { getWindowParameters } from './lib/get_window_parameters'; -export type { WindowParameters } from './lib/get_window_parameters'; -export { streamFactory } from './lib/stream_factory'; -export { useFetchStream } from './lib/use_fetch_stream'; +export { getWindowParameters } from './get_window_parameters'; +export type { WindowParameters } from './get_window_parameters'; +export { streamFactory } from './stream_factory'; +export { useFetchStream } from './use_fetch_stream'; export type { UseFetchStreamCustomReducerParams, UseFetchStreamParamsDefault, -} from './lib/use_fetch_stream'; +} from './use_fetch_stream'; diff --git a/x-pack/packages/ml/aiops_utils/src/lib/stream_factory.test.ts b/x-pack/packages/ml/aiops_utils/src/stream_factory.test.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/stream_factory.test.ts rename to x-pack/packages/ml/aiops_utils/src/stream_factory.test.ts diff --git a/x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts b/x-pack/packages/ml/aiops_utils/src/stream_factory.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/stream_factory.ts rename to x-pack/packages/ml/aiops_utils/src/stream_factory.ts diff --git a/x-pack/packages/ml/aiops_utils/src/lib/string_reducer.ts b/x-pack/packages/ml/aiops_utils/src/string_reducer.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/string_reducer.ts rename to x-pack/packages/ml/aiops_utils/src/string_reducer.ts diff --git a/x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts b/x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts similarity index 100% rename from x-pack/packages/ml/aiops_utils/src/lib/use_fetch_stream.ts rename to x-pack/packages/ml/aiops_utils/src/use_fetch_stream.ts diff --git a/x-pack/packages/ml/aiops_utils/tsconfig.json b/x-pack/packages/ml/aiops_utils/tsconfig.json index dc20b641b1989..d10eb479b3697 100644 --- a/x-pack/packages/ml/aiops_utils/tsconfig.json +++ b/x-pack/packages/ml/aiops_utils/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/x-pack/packages/ml/is_populated_object/BUILD.bazel b/x-pack/packages/ml/is_populated_object/BUILD.bazel index e89b39af7c986..16b8ebfd5a60b 100644 --- a/x-pack/packages/ml/is_populated_object/BUILD.bazel +++ b/x-pack/packages/ml/is_populated_object/BUILD.bazel @@ -73,6 +73,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/x-pack/packages/ml/is_populated_object/tsconfig.json b/x-pack/packages/ml/is_populated_object/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/x-pack/packages/ml/is_populated_object/tsconfig.json +++ b/x-pack/packages/ml/is_populated_object/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/x-pack/packages/ml/string_hash/BUILD.bazel b/x-pack/packages/ml/string_hash/BUILD.bazel index 50e89a8975b51..2540c3e3471e5 100644 --- a/x-pack/packages/ml/string_hash/BUILD.bazel +++ b/x-pack/packages/ml/string_hash/BUILD.bazel @@ -73,6 +73,7 @@ ts_project( srcs = SRCS, deps = TYPES_DEPS, declaration = True, + declaration_map = True, emit_declaration_only = True, out_dir = "target_types", root_dir = "src", diff --git a/x-pack/packages/ml/string_hash/tsconfig.json b/x-pack/packages/ml/string_hash/tsconfig.json index 97a3644c3c703..39d3c7097814a 100644 --- a/x-pack/packages/ml/string_hash/tsconfig.json +++ b/x-pack/packages/ml/string_hash/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../../tsconfig.bazel.json", "compilerOptions": { "declaration": true, + "declarationMap": true, "emitDeclarationOnly": true, "outDir": "target_types", "rootDir": "src", diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes.tsx index b23a3a7b82a79..982f085b15415 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes.tsx @@ -10,7 +10,8 @@ import React, { useEffect, FC } from 'react'; import { EuiCodeBlock, EuiSpacer, EuiText } from '@elastic/eui'; import type { DataView } from '@kbn/data-views-plugin/public'; -import { useFetchStream, ProgressControls } from '@kbn/aiops-utils'; +import { ProgressControls } from '@kbn/aiops-components'; +import { useFetchStream } from '@kbn/aiops-utils'; import type { WindowParameters } from '@kbn/aiops-utils'; import { useKibana } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts b/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts index d8d2b1de4d302..dc0c486deeed9 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.test.ts @@ -14,10 +14,9 @@ describe('service logs', () => { expect( getInfrastructureKQLFilter( { - serviceInfrastructure: { - containerIds: [], - hostNames: [], - }, + containerIds: [], + hostNames: [], + podNames: [], }, serviceName ) @@ -28,10 +27,9 @@ describe('service logs', () => { expect( getInfrastructureKQLFilter( { - serviceInfrastructure: { - containerIds: ['foo', 'bar'], - hostNames: ['baz', `quz`], - }, + containerIds: ['foo', 'bar'], + hostNames: ['baz', `quz`], + podNames: [], }, serviceName ) @@ -44,10 +42,9 @@ describe('service logs', () => { expect( getInfrastructureKQLFilter( { - serviceInfrastructure: { - containerIds: [], - hostNames: ['baz', `quz`], - }, + containerIds: [], + hostNames: ['baz', `quz`], + podNames: [], }, serviceName ) diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx index e282727f0881e..1ea26c08e7fd6 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx @@ -36,7 +36,7 @@ export function ServiceLogs() { (callApmApi) => { if (start && end) { return callApmApi( - 'GET /internal/apm/services/{serviceName}/infrastructure_attributes_for_logs', + 'GET /internal/apm/services/{serviceName}/infrastructure_attributes', { params: { path: { serviceName }, @@ -55,10 +55,7 @@ export function ServiceLogs() { ); const noInfrastructureData = useMemo(() => { - return ( - isEmpty(data?.serviceInfrastructure?.containerIds) && - isEmpty(data?.serviceInfrastructure?.hostNames) - ); + return isEmpty(data?.containerIds) && isEmpty(data?.hostNames); }, [data]); if (status === FETCH_STATUS.LOADING) { @@ -98,12 +95,12 @@ export function ServiceLogs() { export const getInfrastructureKQLFilter = ( data: - | APIReturnType<'GET /internal/apm/services/{serviceName}/infrastructure_attributes_for_logs'> + | APIReturnType<'GET /internal/apm/services/{serviceName}/infrastructure_attributes'> | undefined, serviceName: string ) => { - const containerIds = data?.serviceInfrastructure?.containerIds ?? []; - const hostNames = data?.serviceInfrastructure?.hostNames ?? []; + const containerIds = data?.containerIds ?? []; + const hostNames = data?.hostNames ?? []; const infraAttributes = containerIds.length ? containerIds.map((id) => `${CONTAINER_ID}: "${id}"`) diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx index 36e30dbd36f72..c28b7db3a4aa6 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx @@ -52,7 +52,7 @@ export interface Operation { types: Option[]; } -const versionRegex = new RegExp(/^\d+\.\d+\.\d+$/); +const versionRegex = new RegExp(/latest|^\d+\.\d+\.\d+(\.\w+)?$/); export function validateVersion(version: RuntimeAttachmentSettings['version']) { if (version) { return versionRegex.test(version); diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/java_agent_version_input.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/java_agent_version_input.tsx new file mode 100644 index 0000000000000..a570f6a382dee --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/java_agent_version_input.tsx @@ -0,0 +1,115 @@ +/* + * 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, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexItem, EuiFormRow, EuiLink, EuiComboBox } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { CoreStart } from '@kbn/core/public'; +import { createCallApmApi } from '../../../../services/rest/create_call_apm_api'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { RuntimeAttachmentSettings } from '.'; + +interface Props { + isValid: boolean; + version: string | null; + onChange: (nextVersion: RuntimeAttachmentSettings['version']) => void; +} + +export function JavaAgentVersionInput({ isValid, version, onChange }: Props) { + const { services } = useKibana(); + + useEffect(() => { + createCallApmApi(services as CoreStart); + }, [services]); + + const { data, status } = useFetcher((callApmApi) => { + return callApmApi('GET /internal/apm/fleet/java_agent_versions'); + }, []); + + useEffect(() => { + // When version is not available on the fleet package sets it to "latest" + if (version === null) { + // This is necessary due to a possible bug in Fleet where even thought the form is valid + // the save button is still disabled: https://github.com/elastic/kibana/issues/135131 + setTimeout(() => { + onChange('latest'); + }, 1); + } + }, [version, onChange]); + + const isLoading = status === FETCH_STATUS.LOADING; + const agentJavaVersions = + !isLoading && data?.versions + ? data.versions.map((label) => ({ label })) + : []; + const hasOptions = !!agentJavaVersions.length; + const selectedOption = [{ label: version || '' }]; + + const comboProps = !hasOptions + ? { + // Leaving the onCreateOption out disables custom option + // so only enables it when no options was returned from the API + onCreateOption: (nextVersion: string) => { + onChange(nextVersion); + }, + } + : {}; + + return ( + + + {i18n.translate( + 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText.version', + { defaultMessage: 'version' } + )} + + ), + }} + /> + } + > + { + const nextVersion = selectedOptions[0]?.label; + onChange(nextVersion || ''); + }} + {...comboProps} + /> + + + ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx index 6d702389605d2..fd3ee2f3e4552 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx @@ -7,18 +7,38 @@ import { Meta, Story } from '@storybook/react'; import React, { useState } from 'react'; +import { CoreStart } from '@kbn/core/public'; +import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; import { RuntimeAttachment } from '.'; import { JavaRuntimeAttachment } from './supported_agents/java_runtime_attachment'; +import { createCallApmApi } from '../../../../services/rest/create_call_apm_api'; + +const coreMock = { + http: { + get: async () => ({ + versions: ['1.1.0', '1.1.2', '2.0.1'], + latest: '2.0.1', + }), + }, + notifications: { toasts: { add: () => {} } }, + uiSettings: { get: () => {} }, +} as unknown as CoreStart; + +const KibanaReactContext = createKibanaReactContext(coreMock); const stories: Meta<{}> = { title: 'fleet/Runtime agent attachment', component: RuntimeAttachment, decorators: [ (StoryComponent) => { + createCallApmApi(coreMock); + return ( -
- -
+ +
+ +
+
); }, ], diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx index 1934a609f59bc..dbf6cc249595c 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx @@ -18,19 +18,14 @@ import { EuiDraggable, EuiIcon, DropResult, - EuiFormRow, - EuiFieldText, - EuiLink, } from '@elastic/eui'; import React, { ReactNode } from 'react'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DiscoveryRule } from './discovery_rule'; import { DefaultDiscoveryRule } from './default_discovery_rule'; import { EditDiscoveryRule } from './edit_discovery_rule'; import { IDiscoveryRuleList, Operation, RuntimeAttachmentSettings } from '.'; +import { JavaAgentVersionInput } from './java_agent_version_input'; interface Props { isEnabled: boolean; @@ -85,9 +80,6 @@ export function RuntimeAttachment({ onChangeVersion, isValidVersion, }: Props) { - const { - services: { docLinks }, - } = useKibana(); return (
{showUnsavedWarning && ( @@ -123,50 +115,11 @@ export function RuntimeAttachment({ {isEnabled && ( - - - {i18n.translate( - 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText.version', - { defaultMessage: 'version' } - )} - - ), - }} - /> - } - > - { - const nextVersion = e.target.value; - onChangeVersion(isEmpty(nextVersion) ? null : nextVersion); - }} - placeholder={i18n.translate( - 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.placeHolder', - { defaultMessage: 'Add a version' } - )} - /> - - + )} {isEnabled && ( diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/validate_java_version.test.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/validate_java_version.test.ts new file mode 100644 index 0000000000000..b0553a85d8876 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/validate_java_version.test.ts @@ -0,0 +1,24 @@ +/* + * 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 { validateVersion } from '.'; + +describe('validateVersion', () => { + describe('invalid versions', () => { + ['foo', '123', '1.x.2', 'last', '#', '', '1.2', null].map((invalidInput) => + it(`${invalidInput}: is an invalid version`, () => { + expect(validateVersion(invalidInput)).toBeFalsy(); + }) + ); + }); + describe('valid versions', () => { + ['1.234.5', '1.2.3', '0.0.0', 'latest'].map((validVersion) => + it(`${validVersion}: is a valid version`, () => { + expect(validateVersion(validVersion)).toBeTruthy(); + }) + ); + }); +}); diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index 6c853ea02a34b..5058b86249f2d 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -99,7 +99,7 @@ export const config: PluginConfigDescriptor = { { level: 'warning' } ), renameFromRoot( - 'xpack.apm.maxServiceSelections', + 'xpack.apm.maxServiceSelection', `uiSettings.overrides[${maxSuggestions}]`, { level: 'warning' } ), diff --git a/x-pack/plugins/apm/server/routes/fleet/get_java_agent_versions.test.ts b/x-pack/plugins/apm/server/routes/fleet/get_java_agent_versions.test.ts new file mode 100644 index 0000000000000..825b35418c378 --- /dev/null +++ b/x-pack/plugins/apm/server/routes/fleet/get_java_agent_versions.test.ts @@ -0,0 +1,53 @@ +/* + * 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. + */ + +jest.mock('node-fetch'); +import fetch from 'node-fetch'; +const { Response } = jest.requireActual('node-fetch'); +import { getJavaAgentVersionsFromRegistry } from './get_java_agent_versions'; + +const javaVersionsXML = ` + + co.elastic.apm + elastic-apm-agent + + 1.32.0 + 1.32.0 + + 1.30.1 + 1.31.0 + 1.32.0 + + 20220613135218 + + +`; + +describe('getJavaAgentVersionsFromRegistry', () => { + const mockFetch = fetch as jest.MockedFunction; + describe('With valid API return type', () => { + it('returns latest as first option', async () => { + mockFetch.mockReturnValue(new Response(javaVersionsXML)); + const versions = await getJavaAgentVersionsFromRegistry(); + expect(versions?.[0]).toEqual('latest'); + }); + + it('returns versions in descending order', async () => { + mockFetch.mockReturnValue(new Response(javaVersionsXML)); + const versions = await getJavaAgentVersionsFromRegistry(); + expect(versions).toEqual(['latest', '1.32.0', '1.31.0', '1.30.1']); + }); + }); + + describe('With invalid API return type', () => { + it('returns versions in descending order', async () => { + mockFetch.mockReturnValue(new Response(`404`)); + const versions = await getJavaAgentVersionsFromRegistry(); + expect(versions).toBeUndefined(); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/routes/fleet/get_java_agent_versions.ts b/x-pack/plugins/apm/server/routes/fleet/get_java_agent_versions.ts new file mode 100644 index 0000000000000..db4c02bcc450e --- /dev/null +++ b/x-pack/plugins/apm/server/routes/fleet/get_java_agent_versions.ts @@ -0,0 +1,26 @@ +/* + * 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 fetch from 'node-fetch'; + +const mavenJavaAgentUrl = + 'https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/maven-metadata.xml'; + +const versionRegex = /(\d+\.\d+\.\d+.*?)<\/version>/gm; + +export async function getJavaAgentVersionsFromRegistry() { + const response = await (await fetch(mavenJavaAgentUrl)).text(); + const matchedVersions = [...response.matchAll(versionRegex)]; + + if (!matchedVersions.length) { + return undefined; + } + return matchedVersions + .map((aMatch) => aMatch[1]) + .concat(['latest']) + .reverse(); +} diff --git a/x-pack/plugins/apm/server/routes/fleet/route.ts b/x-pack/plugins/apm/server/routes/fleet/route.ts index 3c3d27b96c33d..418f85a0d53b9 100644 --- a/x-pack/plugins/apm/server/routes/fleet/route.ts +++ b/x-pack/plugins/apm/server/routes/fleet/route.ts @@ -26,6 +26,7 @@ import { getInternalSavedObjectsClient } from '../../lib/helpers/get_internal_sa import { setupRequest } from '../../lib/helpers/setup_request'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { getLatestApmPackage } from './get_latest_apm_package'; +import { getJavaAgentVersionsFromRegistry } from './get_java_agent_versions'; const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/fleet/has_apm_policies', @@ -260,6 +261,17 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ }, }); +const javaAgentVersions = createApmServerRoute({ + endpoint: 'GET /internal/apm/fleet/java_agent_versions', + options: { tags: [] }, + handler: async (): Promise<{ versions: string[] | undefined }> => { + const versions = await getJavaAgentVersionsFromRegistry(); + return { + versions, + }; + }, +}); + export const apmFleetRouteRepository = { ...hasFleetDataRoute, ...fleetAgentsRoute, @@ -267,6 +279,7 @@ export const apmFleetRouteRepository = { ...getUnsupportedApmServerSchemaRoute, ...getMigrationCheckRoute, ...createCloudApmPackagePolicyRoute, + ...javaAgentVersions, }; const FLEET_SECURITY_REQUIRED_MESSAGE = i18n.translate( diff --git a/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts b/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts deleted file mode 100644 index e4e44ac44082b..0000000000000 --- a/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts +++ /dev/null @@ -1,78 +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 { rangeQuery, kqlQuery } from '@kbn/observability-plugin/server'; -import { Setup } from '../../lib/helpers/setup_request'; -import { environmentQuery } from '../../../common/utils/environment_query'; -import { ProcessorEvent } from '../../../common/processor_event'; -import { - SERVICE_NAME, - CONTAINER_ID, - HOST_NAME, -} from '../../../common/elasticsearch_fieldnames'; - -export const getServiceInfrastructure = async ({ - kuery, - serviceName, - environment, - setup, - start, - end, -}: { - kuery: string; - serviceName: string; - environment: string; - setup: Setup; - start: number; - end: number; -}) => { - const { apmEventClient } = setup; - - const response = await apmEventClient.search('get_service_infrastructure', { - apm: { - events: [ProcessorEvent.metric], - }, - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - ...rangeQuery(start, end), - ...environmentQuery(environment), - ...kqlQuery(kuery), - ], - }, - }, - aggs: { - containerIds: { - terms: { - field: CONTAINER_ID, - size: 500, - }, - }, - hostNames: { - terms: { - field: HOST_NAME, - size: 500, - }, - }, - }, - }, - }); - - return { - containerIds: - response.aggregations?.containerIds?.buckets.map( - (bucket) => bucket.key as string - ) ?? [], - hostNames: - response.aggregations?.hostNames?.buckets.map( - (bucket) => bucket.key as string - ) ?? [], - }; -}; diff --git a/x-pack/plugins/apm/server/routes/services/route.ts b/x-pack/plugins/apm/server/routes/services/route.ts index bc224b1315f8c..ce9184d8397f5 100644 --- a/x-pack/plugins/apm/server/routes/services/route.ts +++ b/x-pack/plugins/apm/server/routes/services/route.ts @@ -33,7 +33,6 @@ import { getServiceTransactionTypes } from './get_service_transaction_types'; import { getThroughput } from './get_throughput'; import { getServiceProfilingStatistics } from './profiling/get_service_profiling_statistics'; import { getServiceProfilingTimeline } from './profiling/get_service_profiling_timeline'; -import { getServiceInfrastructure } from './get_service_infrastructure'; import { withApmSpan } from '../../utils/with_apm_span'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { @@ -1130,43 +1129,6 @@ const serviceProfilingStatisticsRoute = createApmServerRoute({ }, }); -// TODO: remove this endpoint in favour of -const serviceInfrastructureRoute = createApmServerRoute({ - endpoint: - 'GET /internal/apm/services/{serviceName}/infrastructure_attributes_for_logs', - params: t.type({ - path: t.type({ - serviceName: t.string, - }), - query: t.intersection([kueryRt, rangeRt, environmentRt]), - }), - options: { tags: ['access:apm'] }, - handler: async ( - resources - ): Promise<{ - serviceInfrastructure: { containerIds: string[]; hostNames: string[] }; - }> => { - const setup = await setupRequest(resources); - - const { params } = resources; - - const { - path: { serviceName }, - query: { environment, kuery, start, end }, - } = params; - - const serviceInfrastructure = await getServiceInfrastructure({ - setup, - serviceName, - environment, - kuery, - start, - end, - }); - return { serviceInfrastructure }; - }, -}); - const serviceAnomalyChartsRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/services/{serviceName}/anomaly_charts', params: t.type({ @@ -1298,7 +1260,6 @@ export const serviceRouteRepository = { ...serviceDependenciesBreakdownRoute, ...serviceProfilingTimelineRoute, ...serviceProfilingStatisticsRoute, - ...serviceInfrastructureRoute, ...serviceAnomalyChartsRoute, ...sortedAndFilteredServicesRoute, }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts index 204021564d1f0..aee62f2c3de2a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/external/embeddable.ts @@ -56,12 +56,12 @@ export function embeddableFunctionFactory({ migrateFn: MigrateFunction ): MigrateFunction => (state: ExpressionAstFunction): ExpressionAstFunction => { - const embeddableInput = decode(state.arguments.config[0] as string); + const embeddableInput = decode(state.arguments.config[0] as string) as EmbeddableInput; const embeddableType = state.arguments.type[0]; - if (embeddableInput.explicitInput.attributes || embeddableInput.explicitInput.savedVis) { - const migratedInput = migrateFn({ ...embeddableInput, type: embeddableType }); + if (embeddableInput.savedObjectId === undefined) { + const migratedInput = migrateFn({ ...embeddableInput, type: embeddableType as string }); state.arguments.config[0] = encode(migratedInput); state.arguments.type[0] = migratedInput.type as string; } diff --git a/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js b/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js index 8228593cd4f3a..804c9f50cd78f 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/integration_utils.js @@ -11,8 +11,9 @@ import { addElement, removeElements, setMultiplePositions } from '../../state/ac import { selectToplevelNodes } from '../../state/actions/transient'; import { arrayToMap, flatten, identity } from '../../lib/aeroelastic/functional'; import { getLocalTransformMatrix } from '../../lib/aeroelastic/layout_functions'; +import { isGroupId } from '../../lib/workpad'; import { matrixToAngle } from '../../lib/aeroelastic/matrix'; -import { isGroupId, elementToShape } from './positioning_utils'; +import { elementToShape } from './positioning_utils'; export * from './positioning_utils'; const shapeToElement = (shape) => ({ diff --git a/x-pack/plugins/canvas/public/components/workpad_page/positioning_utils.ts b/x-pack/plugins/canvas/public/components/workpad_page/positioning_utils.ts index 1bba5e98b5a40..2bf1971439623 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/positioning_utils.ts +++ b/x-pack/plugins/canvas/public/components/workpad_page/positioning_utils.ts @@ -7,8 +7,7 @@ import { PositionedElement, ElementPosition } from '../../../types'; import { multiply, rotateZ, translate } from '../../lib/aeroelastic/matrix'; - -export const isGroupId = (id: string) => id.startsWith('group'); +import { isGroupId } from '../../lib/workpad'; const headerData = (id: string) => isGroupId(id) diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js index 247d9490bebf5..0a74a3df3385a 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_static_page/static_workpad_page.js @@ -8,7 +8,7 @@ import React, { PureComponent } from 'react'; import { ElementWrapper } from '../../element_wrapper'; import { staticWorkpadPagePropTypes } from '../prop_types'; -import { isGroupId } from '../positioning_utils'; +import { isGroupId } from '../../../lib/workpad'; export class StaticWorkpadPage extends PureComponent { static propTypes = staticWorkpadPagePropTypes; diff --git a/x-pack/plugins/canvas/public/lib/workpad.ts b/x-pack/plugins/canvas/public/lib/workpad.ts new file mode 100644 index 0000000000000..a086adfcc4950 --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/workpad.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 const isGroupId = (id: string) => id.startsWith('group'); diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index eec01d881b9ac..eb9dc23faac4a 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -10,6 +10,7 @@ import immutable from 'object-path-immutable'; import { get, pick, cloneDeep, without, last, debounce } from 'lodash'; import { toExpression, safeElementFromExpression } from '@kbn/interpreter'; import { createThunk } from '../../lib/create_thunk'; +import { isGroupId } from '../../lib/workpad'; import { getPages, getWorkpadVariablesAsObject, @@ -450,7 +451,8 @@ export const addElement = createThunk('addElement', ({ dispatch }, pageId, eleme // refresh all elements if there's a filter, otherwise just render the new element if (element.filter) { dispatch(fetchAllRenderables()); - } else { + // element, which represents the group, should not be rendered. Its elements are rendered separately. + } else if (!isGroupId(newElement.id)) { dispatch(fetchRenderable(newElement)); } diff --git a/x-pack/plugins/canvas/public/state/reducers/pages.js b/x-pack/plugins/canvas/public/state/reducers/pages.js index 1565e73da91bd..6702b4a95229c 100644 --- a/x-pack/plugins/canvas/public/state/reducers/pages.js +++ b/x-pack/plugins/canvas/public/state/reducers/pages.js @@ -12,7 +12,7 @@ import { getId } from '../../lib/get_id'; import { getDefaultPage } from '../defaults'; import * as actions from '../actions/pages'; import { getSelectedPageIndex } from '../selectors/workpad'; -import { isGroupId } from '../../components/workpad_page/positioning_utils'; +import { isGroupId } from '../../lib/workpad'; const { set, del, insert } = immutable; diff --git a/x-pack/plugins/cases/README.md b/x-pack/plugins/cases/README.md index 98eb504ff7094..62892c143146f 100644 --- a/x-pack/plugins/cases/README.md +++ b/x-pack/plugins/cases/README.md @@ -48,7 +48,7 @@ To initialize the `CasesContext` you can use this code: // somewhere high on your plugin render tree {/* or something similar */} @@ -57,11 +57,11 @@ To initialize the `CasesContext` you can use this code: props: -| prop | type | description | -| --------------------- | --------------- | -------------------------------------------------------------- | -| PLUGIN_CASES_OWNER_ID | `string` | The owner string for your plugin. e.g: securitySolution | -| CASES_USER_CAN_CRUD | `boolean` | Defines if the user has access to cases to CRUD | -| CASES_FEATURES | `CasesFeatures` | `CasesFeatures` object defining the features to enable/disable | +| prop | type | description | +| --------------------- | ------------------- | -------------------------------------------------------------- | +| PLUGIN_CASES_OWNER_ID | `string` | The owner string for your plugin. e.g: securitySolution | +| CASES_PERMISSIONS | `CasesPermissions` | `CasesPermissions` object defining the user's permissions | +| CASES_FEATURES | `CasesFeatures` | `CasesFeatures` object defining the features to enable/disable | ### Cases UI client @@ -83,7 +83,10 @@ const { cases } = useKibana().services; // call in the return as you would any component cases.getCases({ basePath: '/investigate/cases', - userCanCrud: true, + permissions: { + all: true, + read: true, + }, owner: ['securitySolution'], features: { alerts: { sync: false }, metrics: ['alerts.count', 'lifespan'] } timelineIntegration: { @@ -206,7 +209,7 @@ Arguments: | Property | Description | | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| userCanCrud | `boolean;` user permissions to crud | +| permissions | `CasesPermissions` object defining the user's permissions | | owner | `string[];` owner ids of the cases | | basePath | `string;` path to mount the Cases router on top of | | useFetchAlertData | `(alertIds: string[]) => [boolean, Record];` fetch alerts | @@ -236,7 +239,7 @@ Arguments: | Property | Description | | --------------- | ---------------------------------------------------------------------------------- | -| userCanCrud | `boolean;` user permissions to crud | +| permissions | `CasesPermissions` object defining the user's permissions | | owner | `string[];` owner ids of the cases | | alertData? | `Omit;` alert data to post to case | | hiddenStatuses? | `CaseStatuses[];` array of hidden statuses | @@ -253,7 +256,7 @@ Arguments: | Property | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------ | -| userCanCrud | `boolean;` user permissions to crud | +| permissions | `CasesPermissions` object defining the user's permissions | | owner | `string[];` owner ids of the cases | | onClose | `() => void;` callback when create case is canceled | | onSuccess | `(theCase: Case) => Promise;` callback passing newly created case after pushCaseToExternalService is called | @@ -267,11 +270,11 @@ UI component: Arguments: -| Property | Description | -| -------------- | ------------------------------------------- | -| userCanCrud | `boolean;` user permissions to crud | -| owner | `string[];` owner ids of the cases | -| maxCasesToShow | `number;` number of cases to show in widget | +| Property | Description | +| -------------- | ---------------------------------------------------------- | +| permissions | `CasesPermissions` object defining the user's permissions | +| owner | `string[];` owner ids of the cases | +| maxCasesToShow | `number;` number of cases to show in widget | UI component: ![Recent Cases Component][recent-cases-img] @@ -289,7 +292,7 @@ Arguments: | Property | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------ | -| userCanCrud | `boolean;` user permissions to crud | +| permissions | `CasesPermissions` object defining the user's permissions | | onClose | `() => void;` callback when create case is canceled | | onSuccess | `(theCase: Case) => Promise;` callback passing newly created case after pushCaseToExternalService is called | | afterCaseCreated? | `(theCase: Case) => Promise;` callback passing newly created case before pushCaseToExternalService is called | diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index fdd11e2f3caf5..24cdc4a35303b 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -2574,6 +2574,66 @@ } ] }, + "/api/cases/tags": { + "get": { + "summary": "Aggregates and returns a list of case tags.", + "description": "You must have read privileges for the **Cases** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "in": "query", + "name": "owner", + "description": "A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read.", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/owners" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/owners" + } + } + ] + } + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "getTagsResponse": { + "$ref": "#/components/examples/get_tags_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, "/api/cases/{caseId}/comments": { "post": { "summary": "Adds a comment or alert to a case.", @@ -5834,6 +5894,69 @@ } ] }, + "/s/{spaceId}/api/cases/tags": { + "get": { + "summary": "Aggregates and returns a list of case tags.", + "description": "You must have read privileges for the **Cases*** feature in the **Management**, **Observability**, or **Security** section of the Kibana feature privileges, depending on the owner of the cases you're seeking.\n", + "tags": [ + "cases", + "kibana" + ], + "parameters": [ + { + "$ref": "#/components/parameters/space_id" + }, + { + "in": "query", + "name": "owner", + "description": "A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read.", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/owners" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/owners" + } + } + ] + } + } + ], + "responses": { + "200": { + "description": "Indicates a successful call.", + "content": { + "application/json; charset=utf-8": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "getTagsResponse": { + "$ref": "#/components/examples/get_tags_response" + } + } + } + } + } + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, + "servers": [ + { + "url": "https://localhost:5601" + } + ] + }, "/s/{spaceId}/api/cases/{caseId}/comments": { "post": { "summary": "Adds a comment or alert to a case.", @@ -7307,6 +7430,15 @@ } ] }, + "get_tags_response": { + "summary": "A list of tags that are used in cases", + "value": [ + "observability", + "security", + "tag 1", + "tag 2" + ] + }, "add_comment_request": { "summary": "Adds a comment to a case.", "value": { diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index 8eb4b57d56270..bf9f7823d0950 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -2173,6 +2173,45 @@ paths: - url: https://localhost:5601 servers: - url: https://localhost:5601 + /api/cases/tags: + get: + summary: Aggregates and returns a list of case tags. + description: > + You must have read privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're seeking. + tags: + - cases + - kibana + parameters: + - in: query + name: owner + description: >- + A filter to limit the retrieved case statistics to a specific set of + applications. If this parameter is omitted, the response contains + tags from all cases that the user has access to read. + schema: + oneOf: + - $ref: '#/components/schemas/owners' + - type: array + items: + $ref: '#/components/schemas/owners' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: array + items: + type: string + examples: + getTagsResponse: + $ref: '#/components/examples/get_tags_response' + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 /api/cases/{caseId}/comments: post: summary: Adds a comment or alert to a case. @@ -4886,6 +4925,46 @@ paths: - url: https://localhost:5601 servers: - url: https://localhost:5601 + /s/{spaceId}/api/cases/tags: + get: + summary: Aggregates and returns a list of case tags. + description: > + You must have read privileges for the **Cases*** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're seeking. + tags: + - cases + - kibana + parameters: + - $ref: '#/components/parameters/space_id' + - in: query + name: owner + description: >- + A filter to limit the retrieved case statistics to a specific set of + applications. If this parameter is omitted, the response contains + tags from all cases that the user has access to read. + schema: + oneOf: + - $ref: '#/components/schemas/owners' + - type: array + items: + $ref: '#/components/schemas/owners' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: array + items: + type: string + examples: + getTagsResponse: + $ref: '#/components/examples/get_tags_response' + servers: + - url: https://localhost:5601 + servers: + - url: https://localhost:5601 /s/{spaceId}/api/cases/{caseId}/comments: post: summary: Adds a comment or alert to a case. @@ -6072,6 +6151,13 @@ components: - username: user2 full_name: User 2 email: user2@elastic.co + get_tags_response: + summary: A list of tags that are used in cases + value: + - observability + - security + - tag 1 + - tag 2 add_comment_request: summary: Adds a comment to a case. value: diff --git a/x-pack/plugins/cases/docs/openapi/components/examples/get_tags_response.yaml b/x-pack/plugins/cases/docs/openapi/components/examples/get_tags_response.yaml new file mode 100644 index 0000000000000..e2f14cf5eb96c --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/components/examples/get_tags_response.yaml @@ -0,0 +1,8 @@ +summary: A list of tags that are used in cases +value: + [ + "observability", + "security", + "tag 1", + "tag 2" + ] \ No newline at end of file diff --git a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml index 4e28aef54c4c9..c476e67c7ad6d 100644 --- a/x-pack/plugins/cases/docs/openapi/entrypoint.yaml +++ b/x-pack/plugins/cases/docs/openapi/entrypoint.yaml @@ -33,8 +33,8 @@ paths: $ref: 'paths/api@cases@reporters.yaml' # '/api/cases/status': # $ref: 'paths/api@cases@status.yaml' -# '/api/cases/tags': -# $ref: 'paths/api@cases@tags.yaml' + '/api/cases/tags': + $ref: 'paths/api@cases@tags.yaml' # '/api/cases/{caseId}': # $ref: 'paths/api@cases@{caseid}.yaml' # '/api/cases/{caseId}/alerts': @@ -64,8 +64,8 @@ paths: $ref: 'paths/s@{spaceid}@api@cases@reporters.yaml' # '/s/{spaceId}/api/cases/status': # $ref: 'paths/s@{spaceid}@api@cases@status.yaml' - # '/s/{spaceId}/api/cases/tags': - # $ref: 'paths/s@{spaceid}@api@cases@tags.yaml' + '/s/{spaceId}/api/cases/tags': + $ref: 'paths/s@{spaceid}@api@cases@tags.yaml' # '/s/{spaceId}/api/cases/{caseId}': # $ref: 'paths/s@{spaceid}@api@cases@{caseid}.yaml' # '/s/{spaceId}/api/cases/{caseId}/alerts': diff --git a/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml b/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml new file mode 100644 index 0000000000000..68cccc0c8f723 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/api@cases@tags.yaml @@ -0,0 +1,35 @@ +get: + summary: Aggregates and returns a list of case tags. + description: > + You must have read privileges for the **Cases** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're seeking. + tags: + - cases + - kibana + parameters: + - in: query + name: owner + description: A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. + schema: + oneOf: + - $ref: '../components/schemas/owners.yaml' + - type: array + items: + $ref: '../components/schemas/owners.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: array + items: + type: string + examples: + getTagsResponse: + $ref: '../components/examples/get_tags_response.yaml' + servers: + - url: https://localhost:5601 +servers: + - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml new file mode 100644 index 0000000000000..7798b74296126 --- /dev/null +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@tags.yaml @@ -0,0 +1,37 @@ +get: + summary: Aggregates and returns a list of case tags. + description: > + You must have read privileges for the **Cases*** feature in the + **Management**, **Observability**, or **Security** section of the Kibana + feature privileges, depending on the owner of the cases you're seeking. + tags: + - cases + - kibana + parameters: + - $ref: '../components/parameters/space_id.yaml' + - in: query + name: owner + description: A filter to limit the retrieved case statistics to a specific set of applications. If this parameter is omitted, the response contains tags from all cases that the user has access to read. + schema: + oneOf: + - $ref: '../components/schemas/owners.yaml' + - type: array + items: + $ref: '../components/schemas/owners.yaml' + responses: + '200': + description: Indicates a successful call. + content: + application/json; charset=utf-8: + schema: + type: array + items: + type: string + examples: + getTagsResponse: + $ref: '../components/examples/get_tags_response.yaml' + + servers: + - url: https://localhost:5601 +servers: + - url: https://localhost:5601 diff --git a/x-pack/plugins/cases/public/client/helpers/capabilities.ts b/x-pack/plugins/cases/public/client/helpers/capabilities.ts new file mode 100644 index 0000000000000..652678d0add28 --- /dev/null +++ b/x-pack/plugins/cases/public/client/helpers/capabilities.ts @@ -0,0 +1,23 @@ +/* + * 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 CasesPermissions { + all: boolean; + read: boolean; +} + +export const getUICapabilities = ( + featureCapabilities: Partial>> +): CasesPermissions => { + const read = !!featureCapabilities?.read_cases; + const all = !!featureCapabilities?.crud_cases; + + return { + all, + read, + }; +}; diff --git a/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx b/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx index a40c92643ec34..37145c59b94ad 100644 --- a/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx +++ b/x-pack/plugins/cases/public/client/ui/get_all_cases_selector_modal.tsx @@ -22,12 +22,12 @@ const AllCasesSelectorModalLazy: React.FC = lazy( export const getAllCasesSelectorModalLazy = ({ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, hiddenStatuses, onRowClick, onClose, }: GetAllCasesSelectorModalPropsInternal) => ( - + }> = lazy(() => import('../../component export const getCasesLazy = ({ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, basePath, onComponentInitialized, actionsNavigation, @@ -34,7 +34,7 @@ export const getCasesLazy = ({ value={{ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, basePath, features, releasePhase, diff --git a/x-pack/plugins/cases/public/client/ui/get_cases_context.tsx b/x-pack/plugins/cases/public/client/ui/get_cases_context.tsx index 699b2ea4c515c..48367bb7672c2 100644 --- a/x-pack/plugins/cases/public/client/ui/get_cases_context.tsx +++ b/x-pack/plugins/cases/public/client/ui/get_cases_context.tsx @@ -22,7 +22,7 @@ const CasesProviderLazy: React.FC<{ value: GetCasesContextPropsInternal }> = laz const CasesProviderLazyWrapper = ({ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, features, children, releasePhase, @@ -33,7 +33,7 @@ const CasesProviderLazyWrapper = ({ value={{ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, features, releasePhase, }} diff --git a/x-pack/plugins/cases/public/client/ui/get_create_case_flyout.tsx b/x-pack/plugins/cases/public/client/ui/get_create_case_flyout.tsx index a6eb07a09aaf4..5149b71d19dd4 100644 --- a/x-pack/plugins/cases/public/client/ui/get_create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/client/ui/get_create_case_flyout.tsx @@ -22,14 +22,14 @@ export const CreateCaseFlyoutLazy: React.FC = lazy( export const getCreateCaseFlyoutLazy = ({ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, features, afterCaseCreated, onClose, onSuccess, attachments, }: GetCreateCaseFlyoutPropsInternal) => ( - + }> = lazy( export const getRecentCasesLazy = ({ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, maxCasesToShow, }: GetRecentCasesPropsInternal) => ( - + }> diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx b/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx index 0f6a1e0035e5c..3fb2ccf2ddc6b 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.test.tsx @@ -23,7 +23,7 @@ describe('hooks', () => { expect(result.current).toEqual({ actions: { crud: true, read: true }, - generalCases: { crud: true, read: true }, + generalCases: { all: true, read: true }, visualize: { crud: true, read: true }, dashboard: { crud: true, read: true }, }); diff --git a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts index a53a1e9ea452f..b144fcf9e24d2 100644 --- a/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/cases/public/common/lib/kibana/hooks.ts @@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n'; import { AuthenticatedUser } from '@kbn/security-plugin/common/model'; import { NavigateToAppOptions } from '@kbn/core/public'; +import { CasesPermissions, getUICapabilities } from '../../../client/helpers/capabilities'; import { convertToCamelCase } from '../../../api/utils'; import { FEATURE_ID, @@ -166,7 +167,7 @@ interface Capabilities { } interface UseApplicationCapabilities { actions: Capabilities; - generalCases: Capabilities; + generalCases: CasesPermissions; visualize: Capabilities; dashboard: Capabilities; } @@ -179,13 +180,14 @@ interface UseApplicationCapabilities { export const useApplicationCapabilities = (): UseApplicationCapabilities => { const capabilities = useKibana().services?.application?.capabilities; const casesCapabilities = capabilities[FEATURE_ID]; + const permissions = getUICapabilities(casesCapabilities); return useMemo( () => ({ actions: { crud: !!capabilities.actions?.save, read: !!capabilities.actions?.show }, generalCases: { - crud: !!casesCapabilities?.crud_cases, - read: !!casesCapabilities?.read_cases, + all: permissions.all, + read: permissions.read, }, visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show }, dashboard: { @@ -200,8 +202,8 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => { capabilities.dashboard?.show, capabilities.visualize?.save, capabilities.visualize?.show, - casesCapabilities?.crud_cases, - casesCapabilities?.read_cases, + permissions.all, + permissions.read, ] ); }; 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 07ea1694b38e8..14ce43fc580af 100644 --- a/x-pack/plugins/cases/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/cases/public/common/mock/test_providers.tsx @@ -23,13 +23,14 @@ import { import { FieldHook } from '../shared_imports'; import { StartServices } from '../../types'; import { ReleasePhase } from '../../components/types'; +import { CasesPermissions } from '../../client/helpers/capabilities'; import { AttachmentTypeRegistry } from '../../client/attachment_framework/registry'; import { ExternalReferenceAttachmentType } from '../../client/attachment_framework/types'; import { ExternalReferenceAttachmentTypeRegistry } from '../../client/attachment_framework/external_reference_registry'; interface TestProviderProps { children: React.ReactNode; - userCanCrud?: boolean; + permissions?: CasesPermissions; features?: CasesFeatures; owner?: string[]; releasePhase?: ReleasePhase; @@ -45,7 +46,7 @@ const TestProvidersComponent: React.FC = ({ children, features, owner = [SECURITY_SOLUTION_OWNER], - userCanCrud = true, + permissions = allCasesPermissions(), releasePhase = 'ga', externalReferenceAttachmentTypeRegistry = new ExternalReferenceAttachmentTypeRegistry(), }) => { @@ -63,7 +64,7 @@ const TestProvidersComponent: React.FC = ({ ({ eui: euiDarkVars, darkMode: true })}> {children} @@ -92,10 +93,24 @@ export const testQueryClient = new QueryClient({ }, }); +export const buildCasesPermissions = (overrides: Partial = {}) => { + const read = overrides.read ?? true; + const all = overrides.all ?? true; + + return { + all, + read, + }; +}; + +export const allCasesPermissions = () => buildCasesPermissions(); +export const noCasesPermissions = () => buildCasesPermissions({ read: false, all: false }); +export const readCasesPermissions = () => buildCasesPermissions({ all: false }); + export const createAppMockRenderer = ({ features, owner = [SECURITY_SOLUTION_OWNER], - userCanCrud = true, + permissions = allCasesPermissions(), releasePhase = 'ga', externalReferenceAttachmentTypeRegistry = new ExternalReferenceAttachmentTypeRegistry(), }: Omit = {}): AppMockRenderer => { @@ -118,7 +133,7 @@ export const createAppMockRenderer = ({ externalReferenceAttachmentTypeRegistry, features, owner, - userCanCrud, + permissions, releasePhase, }} > diff --git a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx index b9a253adc76f5..18e02ff098314 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.test.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.test.tsx @@ -29,7 +29,6 @@ const createAttachments = jest.fn(); const addCommentProps: AddCommentProps = { id: 'newComment', caseId: '1234', - userCanCrud: true, onCommentSaving, onCommentPosted, showLoading: false, @@ -120,8 +119,8 @@ describe('AddComment ', () => { isLoading: true, })); const wrapper = mount( - - + + ); diff --git a/x-pack/plugins/cases/public/components/add_comment/index.tsx b/x-pack/plugins/cases/public/components/add_comment/index.tsx index f3630d16cda79..235917a504e2e 100644 --- a/x-pack/plugins/cases/public/components/add_comment/index.tsx +++ b/x-pack/plugins/cases/public/components/add_comment/index.tsx @@ -47,7 +47,6 @@ export interface AddCommentRefObject { export interface AddCommentProps { id: string; caseId: string; - userCanCrud?: boolean; onCommentSaving?: () => void; onCommentPosted: (newCase: Case) => void; showLoading?: boolean; @@ -57,20 +56,12 @@ export interface AddCommentProps { export const AddComment = React.memo( forwardRef( ( - { - id, - caseId, - userCanCrud, - onCommentPosted, - onCommentSaving, - showLoading = true, - statusActionButton, - }, + { id, caseId, onCommentPosted, onCommentSaving, showLoading = true, statusActionButton }, ref ) => { const editorRef = useRef(null); const [focusOnContext, setFocusOnContext] = useState(false); - const { owner } = useCasesContext(); + const { permissions, owner } = useCasesContext(); const { isLoading, createAttachments } = useCreateAttachments(); const { form } = useForm({ @@ -156,7 +147,7 @@ export const AddComment = React.memo( return ( {isLoading && showLoading && } - {userCanCrud && ( + {permissions.all && (
{ handleIsLoading: jest.fn(), isLoadingCases: [], isSelectorView: false, - userCanCrud: true, }; let appMockRenderer: AppMockRenderer; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx index 4dc2223537d6d..887a30212833e 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.tsx @@ -61,7 +61,7 @@ export interface AllCasesListProps { export const AllCasesList = React.memo( ({ hiddenStatuses = [], isSelectorView = false, onRowClick, doRefresh }) => { - const { owner, userCanCrud } = useCasesContext(); + const { owner, permissions } = useCasesContext(); const availableSolutions = useAvailableCasesOwners(); const [refresh, setRefresh] = useState(0); @@ -185,14 +185,13 @@ export const AllCasesList = React.memo( [deselectCases, setFilterOptions, refreshCases, setQueryParams] ); - const showActions = userCanCrud && !isSelectorView; + const showActions = permissions.all && !isSelectorView; const columns = useCasesColumns({ filterStatus: filterOptions.status ?? StatusAll, handleIsLoading, refreshCases, isSelectorView, - userCanCrud, connectors, onRowClick, showSolutionColumn: !hasOwner && availableSolutions.length > 1, @@ -271,7 +270,6 @@ export const AllCasesList = React.memo( sorting={sorting} tableRef={tableRef} tableRowProps={tableRowProps} - userCanCrud={userCanCrud} /> ); diff --git a/x-pack/plugins/cases/public/components/all_cases/columns.tsx b/x-pack/plugins/cases/public/components/all_cases/columns.tsx index 5d161caff5a17..3476be64c09ff 100644 --- a/x-pack/plugins/cases/public/components/all_cases/columns.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/columns.tsx @@ -43,6 +43,7 @@ import type { CasesOwners } from '../../client/helpers/can_use_cases'; import { useCasesFeatures } from '../cases_context/use_cases_features'; import { severities } from '../severity/config'; import { useUpdateCase } from '../../containers/use_update_case'; +import { useCasesContext } from '../cases_context/use_cases_context'; export type CasesColumns = | EuiTableActionsColumnType @@ -61,7 +62,6 @@ export interface GetCasesColumn { handleIsLoading: (a: boolean) => void; refreshCases?: (a?: boolean) => void; isSelectorView: boolean; - userCanCrud: boolean; connectors?: ActionConnector[]; onRowClick?: (theCase: Case) => void; @@ -72,7 +72,6 @@ export const useCasesColumns = ({ handleIsLoading, refreshCases, isSelectorView, - userCanCrud, connectors = [], onRowClick, showSolutionColumn, @@ -88,6 +87,7 @@ export const useCasesColumns = ({ } = useDeleteCases(); const { isAlertsEnabled } = useCasesFeatures(); + const { permissions } = useCasesContext(); const [deleteThisCase, setDeleteThisCase] = useState({ id: '', @@ -319,7 +319,7 @@ export const useCasesColumns = ({ return ( handleDispatchUpdate({ updateKey: 'status', @@ -372,7 +372,7 @@ export const useCasesColumns = ({ }, ] : []), - ...(userCanCrud && !isSelectorView + ...(permissions.all && !isSelectorView ? [ { name: ( diff --git a/x-pack/plugins/cases/public/components/all_cases/header.tsx b/x-pack/plugins/cases/public/components/all_cases/header.tsx index 9a02594a790fa..aeb8a25340811 100644 --- a/x-pack/plugins/cases/public/components/all_cases/header.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/header.tsx @@ -11,23 +11,32 @@ import { HeaderPage } from '../header_page'; import * as i18n from './translations'; import { ErrorMessage } from '../use_push_to_service/callout/types'; import { NavButtons } from './nav_buttons'; +import { useCasesContext } from '../cases_context/use_cases_context'; interface OwnProps { actionsErrors: ErrorMessage[]; - userCanCrud: boolean; } type Props = OwnProps; -export const CasesTableHeader: FunctionComponent = ({ actionsErrors, userCanCrud }) => ( - - - {userCanCrud ? ( - - - - ) : null} - - -); +export const CasesTableHeader: FunctionComponent = ({ actionsErrors }) => { + const { permissions } = useCasesContext(); + + return ( + + + {permissions.all ? ( + + + + ) : null} + + + ); +}; CasesTableHeader.displayName = 'CasesTableHeader'; diff --git a/x-pack/plugins/cases/public/components/all_cases/index.tsx b/x-pack/plugins/cases/public/components/all_cases/index.tsx index 465806135a096..af467a7293239 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.tsx @@ -8,14 +8,12 @@ import React, { useMemo } from 'react'; import { CasesDeepLinkId } from '../../common/navigation'; import { useGetActionLicense } from '../../containers/use_get_action_license'; -import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesBreadcrumbs } from '../use_breadcrumbs'; import { getActionLicenseError } from '../use_push_to_service/helpers'; import { AllCasesList } from './all_cases_list'; import { CasesTableHeader } from './header'; export const AllCases: React.FC = () => { - const { userCanCrud } = useCasesContext(); useCasesBreadcrumbs(CasesDeepLinkId.cases); const { data: actionLicense = null } = useGetActionLicense(); @@ -23,7 +21,7 @@ export const AllCases: React.FC = () => { return ( <> - + ); diff --git a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx index cfa2aeb8482e1..c390e4358ea76 100644 --- a/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/selector_modal/use_cases_add_to_existing_case_modal.test.tsx @@ -61,7 +61,10 @@ describe('use cases add to existing case modal hook', () => { value={{ externalReferenceAttachmentTypeRegistry, owner: ['test'], - userCanCrud: true, + permissions: { + all: true, + read: true, + }, appId: 'test', appTitle: 'jest', basePath: '/jest', diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx index 6d76e009403b8..86f5ea1ad195c 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -22,6 +22,7 @@ import { LinkButton } from '../links'; import { Cases, Case, FilterOptions } from '../../../common/ui/types'; import * as i18n from './translations'; import { useCreateCaseNavigation } from '../../common/navigation'; +import { useCasesContext } from '../cases_context/use_cases_context'; interface CasesTableProps { columns: EuiBasicTableProps['columns']; @@ -42,7 +43,6 @@ interface CasesTableProps { sorting: EuiBasicTableProps['sorting']; tableRef: MutableRefObject; tableRowProps: EuiBasicTableProps['rowProps']; - userCanCrud: boolean; } const Div = styled.div` @@ -68,8 +68,8 @@ export const CasesTable: FunctionComponent = ({ sorting, tableRef, tableRowProps, - userCanCrud, }) => { + const { permissions } = useCasesContext(); const { getCreateCaseUrl, navigateToCreateCase } = useCreateCaseNavigation(); const navigateToCreateCaseClick = useCallback( (ev) => { @@ -109,11 +109,11 @@ export const CasesTable: FunctionComponent = ({ {i18n.NO_CASES}} titleSize="xs" - body={userCanCrud ? i18n.NO_CASES_BODY : i18n.NO_CASES_BODY_READ_ONLY} + body={permissions.all ? i18n.NO_CASES_BODY : i18n.NO_CASES_BODY_READ_ONLY} actions={ - userCanCrud && ( + permissions.all && ( = ({ externalReferenceAttachmentTypeRegistry, owner: [APP_OWNER], useFetchAlertData: () => [false, {}], - userCanCrud: userCapabilities.generalCases.crud, + permissions: userCapabilities.generalCases, basePath: '/', features: { alerts: { enabled: false } }, releasePhase: 'experimental', diff --git a/x-pack/plugins/cases/public/components/app/routes.test.tsx b/x-pack/plugins/cases/public/components/app/routes.test.tsx index 8523b00317713..d8191aa339e6a 100644 --- a/x-pack/plugins/cases/public/components/app/routes.test.tsx +++ b/x-pack/plugins/cases/public/components/app/routes.test.tsx @@ -10,8 +10,9 @@ import React from 'react'; import { MemoryRouterProps } from 'react-router'; import { render, screen, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; -import { TestProviders } from '../../common/mock'; +import { readCasesPermissions, TestProviders } from '../../common/mock'; import { CasesRoutes } from './routes'; +import { CasesPermissions } from '../../client/helpers/capabilities'; jest.mock('../all_cases', () => ({ AllCases: () =>
{'All cases'}
, @@ -29,10 +30,10 @@ const getCaseViewPaths = () => ['/cases/test-id', '/cases/test-id/comment-id']; const renderWithRouter = ( initialEntries: MemoryRouterProps['initialEntries'] = ['/cases'], - userCanCrud = true + permissions?: CasesPermissions ) => { return render( - + [false, {}]} /> @@ -48,8 +49,8 @@ describe('Cases routes', () => { }); // User has read only privileges - it('user can navigate to the all cases page with userCanCrud = false', () => { - renderWithRouter(['/cases'], false); + it('user can navigate to the all cases page with only read permissions', () => { + renderWithRouter(['/cases'], readCasesPermissions()); expect(screen.getByText('All cases')).toBeInTheDocument(); }); }); @@ -68,9 +69,9 @@ describe('Cases routes', () => { ); it.each(getCaseViewPaths())( - 'user can navigate to the cases view page with userCanCrud = false and path: %s', + 'user can navigate to the cases view page with read permissions and path: %s', async (path: string) => { - renderWithRouter([path], false); + renderWithRouter([path], readCasesPermissions()); await waitFor(() => { expect(screen.getByTestId('case-view-loading')).toBeInTheDocument(); }); @@ -84,8 +85,8 @@ describe('Cases routes', () => { expect(screen.getByText('Create case')).toBeInTheDocument(); }); - it('shows the no privileges page if userCanCrud = false', () => { - renderWithRouter(['/cases/create'], false); + it('shows the no privileges page if user is read only', () => { + renderWithRouter(['/cases/create'], readCasesPermissions()); expect(screen.getByText('Privileges required')).toBeInTheDocument(); }); }); @@ -96,8 +97,8 @@ describe('Cases routes', () => { expect(screen.getByText('Configure cases')).toBeInTheDocument(); }); - it('shows the no privileges page if userCanCrud = false', () => { - renderWithRouter(['/cases/configure'], false); + it('shows the no privileges page if user is read only', () => { + renderWithRouter(['/cases/configure'], readCasesPermissions()); expect(screen.getByText('Privileges required')).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/app/routes.tsx b/x-pack/plugins/cases/public/components/app/routes.tsx index 06c92b7d199a4..fae77ef94fb0e 100644 --- a/x-pack/plugins/cases/public/components/app/routes.tsx +++ b/x-pack/plugins/cases/public/components/app/routes.tsx @@ -40,7 +40,7 @@ const CasesRoutesComponent: React.FC = ({ refreshRef, timelineIntegration, }) => { - const { basePath, userCanCrud } = useCasesContext(); + const { basePath, permissions } = useCasesContext(); const { navigateToAllCases } = useAllCasesNavigation(); const { navigateToCaseView } = useCaseViewNavigation(); useReadonlyHeader(); @@ -58,7 +58,7 @@ const CasesRoutesComponent: React.FC = ({ - {userCanCrud ? ( + {permissions.all ? ( = ({ - {userCanCrud ? ( + {permissions.all ? ( ) : ( diff --git a/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx b/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx index 31c9b46ad7ea5..39e7fa39b461f 100644 --- a/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx +++ b/x-pack/plugins/cases/public/components/app/use_readonly_header.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; import { useKibana } from '../../common/lib/kibana'; -import { TestProviders } from '../../common/mock'; +import { readCasesPermissions, TestProviders } from '../../common/mock'; import { useReadonlyHeader } from './use_readonly_header'; const useKibanaMock = useKibana as jest.Mocked; @@ -33,7 +33,9 @@ describe('CaseContainerComponent', () => { it('displays the readonly glasses badge read permissions but not write', () => { renderHook(() => useReadonlyHeader(), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + {children} + ), }); expect(mockedSetBadge).toBeCalledTimes(1); diff --git a/x-pack/plugins/cases/public/components/app/use_readonly_header.ts b/x-pack/plugins/cases/public/components/app/use_readonly_header.ts index eb522dffe4c7b..08aa05ab98ffe 100644 --- a/x-pack/plugins/cases/public/components/app/use_readonly_header.ts +++ b/x-pack/plugins/cases/public/components/app/use_readonly_header.ts @@ -15,19 +15,19 @@ import { useCasesContext } from '../cases_context/use_cases_context'; * This component places a read-only icon badge in the header if user only has read permissions */ export function useReadonlyHeader() { - const { userCanCrud } = useCasesContext(); + const { permissions } = useCasesContext(); const chrome = useKibana().services.chrome; // if the user is read only then display the glasses badge in the global navigation header const setBadge = useCallback(() => { - if (!userCanCrud) { + if (!permissions.all && permissions.read) { chrome.setBadge({ text: i18n.READ_ONLY_BADGE_TEXT, tooltip: i18n.READ_ONLY_BADGE_TOOLTIP, iconType: 'glasses', }); } - }, [chrome, userCanCrud]); + }, [chrome, permissions]); useEffect(() => { setBadge(); diff --git a/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx index 1142f7f27ccf3..f9c92c8222ccf 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/index.test.tsx @@ -42,7 +42,6 @@ describe('CaseActionBar', () => { isLoading: false, onUpdateField, currentExternalIncident: null, - userCanCrud: true, metricsFeatures: [], }; 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 5af9835605de9..dbffb0c338248 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 @@ -29,6 +29,7 @@ import { useCasesFeatures } from '../cases_context/use_cases_features'; import { FormattedRelativePreferenceDate } from '../formatted_date'; import { getStatusDate, getStatusTitle } from './helpers'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; +import { useCasesContext } from '../cases_context/use_cases_context'; const MyDescriptionList = styled(EuiDescriptionList)` ${({ theme }) => css` @@ -45,16 +46,15 @@ const MyDescriptionList = styled(EuiDescriptionList)` export interface CaseActionBarProps { caseData: Case; - userCanCrud: boolean; isLoading: boolean; onUpdateField: (args: OnUpdateFields) => void; } const CaseActionBarComponent: React.FC = ({ caseData, - userCanCrud, isLoading, onUpdateField, }) => { + const { permissions } = useCasesContext(); const { isSyncAlertsEnabled, metricsFeatures } = useCasesFeatures(); const date = useMemo(() => getStatusDate(caseData), [caseData]); const title = useMemo(() => getStatusTitle(caseData.status), [caseData.status]); @@ -107,7 +107,7 @@ const CaseActionBarComponent: React.FC = ({ @@ -134,7 +134,7 @@ const CaseActionBarComponent: React.FC = ({ responsive={false} justifyContent="spaceBetween" > - {userCanCrud && isSyncAlertsEnabled && ( + {permissions.all && isSyncAlertsEnabled && ( = ({ - {userCanCrud && ( + {permissions.all && ( ( showAlertDetails, useFetchAlertData, }) => { - const { userCanCrud, features } = useCasesContext(); + const { features } = useCasesContext(); const { navigateToCaseView } = useCaseViewNavigation(); const { urlParams } = useUrlParams(); const refreshCaseViewPage = useRefreshCaseViewPage(); @@ -171,7 +171,6 @@ export const CaseViewPage = React.memo( data-test-subj="case-view-title" titleNode={ ( > diff --git a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx index 03fb726e1125c..ccf0bd9b475b3 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.tsx @@ -39,7 +39,7 @@ export const CaseViewActivity = ({ showAlertDetails?: (alertId: string, index: string) => void; useFetchAlertData: UseFetchAlertData; }) => { - const { userCanCrud } = useCasesContext(); + const { permissions } = useCasesContext(); const { getCaseViewUrl } = useCaseViewNavigation(); const { data: userActionsData, isLoading: isLoadingUserActions } = useGetCaseUserActions( @@ -133,7 +133,7 @@ export const CaseViewActivity = ({ onShowAlertDetails={onShowAlertDetails} onUpdateField={onUpdateField} statusActionButton={ - userCanCrud ? ( + permissions.all ? ( @@ -150,7 +149,7 @@ export const CaseViewActivity = ({ ) : null} ) : null} diff --git a/x-pack/plugins/cases/public/components/case_view/index.test.tsx b/x-pack/plugins/cases/public/components/case_view/index.test.tsx index 4990174b08dd7..b9213a8eb887f 100644 --- a/x-pack/plugins/cases/public/components/case_view/index.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/index.test.tsx @@ -189,7 +189,6 @@ describe('CaseView', () => { onComponentInitialized: jest.fn(), showAlertDetails: jest.fn(), useFetchAlertData: jest.fn().mockReturnValue([false, alertsHit[0]]), - userCanCrud: true, }} /> ); diff --git a/x-pack/plugins/cases/public/components/cases_context/index.tsx b/x-pack/plugins/cases/public/components/cases_context/index.tsx index ebcdebc012709..de63eefb79cc5 100644 --- a/x-pack/plugins/cases/public/components/cases_context/index.tsx +++ b/x-pack/plugins/cases/public/components/cases_context/index.tsx @@ -7,6 +7,7 @@ import React, { useState, useEffect, useReducer, Dispatch } from 'react'; import { merge } from 'lodash'; +import useDeepCompareEffect from 'react-use/lib/useDeepCompareEffect'; import { DEFAULT_FEATURES } from '../../../common/constants'; import { DEFAULT_BASE_PATH } from '../../common/navigation'; import { useApplication } from './use_application'; @@ -27,7 +28,10 @@ export interface CasesContextValue { owner: string[]; appId: string; appTitle: string; - userCanCrud: boolean; + permissions: { + all: boolean; + read: boolean; + }; basePath: string; features: CasesFeaturesAllRequired; releasePhase: ReleasePhase; @@ -37,7 +41,7 @@ export interface CasesContextValue { export interface CasesContextProps extends Pick< CasesContextValue, - 'owner' | 'userCanCrud' | 'externalReferenceAttachmentTypeRegistry' + 'owner' | 'permissions' | 'externalReferenceAttachmentTypeRegistry' > { basePath?: string; features?: CasesFeatures; @@ -56,7 +60,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ value: { externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, basePath = DEFAULT_BASE_PATH, features = {}, releasePhase = 'ga', @@ -67,7 +71,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ const [value, setValue] = useState(() => ({ externalReferenceAttachmentTypeRegistry, owner, - userCanCrud, + permissions, basePath, /** * The empty object at the beginning avoids the mutation @@ -83,7 +87,14 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ })); /** - * `userCanCrud` prop may change by the parent plugin. + * Only update the context if the nested permissions fields changed, this avoids a rerender when the object's reference + * changes. + */ + useDeepCompareEffect(() => { + setValue((prev) => ({ ...prev, permissions })); + }, [permissions]); + + /** * `appId` and `appTitle` are dynamically retrieved from kibana context. * We need to update the state if any of these values change, the rest of props are never updated. */ @@ -93,10 +104,9 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ ...prev, appId, appTitle, - userCanCrud, })); } - }, [appTitle, appId, userCanCrud]); + }, [appTitle, appId]); return isCasesContextValue(value) ? ( @@ -108,7 +118,7 @@ export const CasesProvider: React.FC<{ value: CasesContextProps }> = ({ CasesProvider.displayName = 'CasesProvider'; function isCasesContextValue(value: CasesContextStateValue): value is CasesContextValue { - return value.appId != null && value.appTitle != null && value.userCanCrud != null; + return value.appId != null && value.appTitle != null && value.permissions != null; } // eslint-disable-next-line import/no-default-export diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx index 890b8683ae6a5..b6c46cbd91731 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.test.tsx @@ -10,7 +10,7 @@ import { ReactWrapper, mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; import { ConfigureCases } from '.'; -import { TestProviders } from '../../common/mock'; +import { noCasesPermissions, TestProviders } from '../../common/mock'; import { Connectors } from './connectors'; import { ClosureOptions } from './closure_options'; @@ -191,7 +191,7 @@ describe('ConfigureCases', () => { test('it disables correctly when the user cannot crud', () => { const newWrapper = mount(, { wrappingComponent: TestProviders, - wrappingComponentProps: { userCanCrud: false }, + wrappingComponentProps: { permissions: noCasesPermissions() }, }); expect(newWrapper.find('button[data-test-subj="dropdown-connectors"]').prop('disabled')).toBe( diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index e7542ba39f382..bf75dd5a828cd 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -51,7 +51,7 @@ const FormWrapper = styled.div` `; export const ConfigureCases: React.FC = React.memo(() => { - const { userCanCrud } = useCasesContext(); + const { permissions } = useCasesContext(); const { triggersActionsUi } = useKibana().services; useCasesBreadcrumbs(CasesDeepLinkId.casesConfigure); @@ -225,7 +225,7 @@ export const ConfigureCases: React.FC = React.memo(() => { @@ -233,13 +233,13 @@ export const ConfigureCases: React.FC = React.memo(() => { {ConnectorAddFlyout} diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx index f701a3e647add..d553295eaef98 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.test.tsx @@ -13,6 +13,7 @@ import React from 'react'; import { CasesContext } from '../../cases_context'; import { CasesContextStoreActionsList } from '../../cases_context/cases_context_reducer'; import { useCasesAddToNewCaseFlyout } from './use_cases_add_to_new_case_flyout'; +import { allCasesPermissions } from '../../../common/mock'; import { ExternalReferenceAttachmentTypeRegistry } from '../../../client/attachment_framework/external_reference_registry'; jest.mock('../../../common/use_cases_toast'); @@ -30,7 +31,7 @@ describe('use cases add to new case flyout hook', () => { value={{ externalReferenceAttachmentTypeRegistry, owner: ['test'], - userCanCrud: true, + permissions: allCasesPermissions(), appId: 'test', appTitle: 'jest', basePath: '/jest', diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx index 6309ce0ebd832..ee8c34faff078 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.test.tsx @@ -11,7 +11,12 @@ import { render, waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { EditConnector, EditConnectorProps } from '.'; -import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; +import { + AppMockRenderer, + createAppMockRenderer, + readCasesPermissions, + TestProviders, +} from '../../common/mock'; import { basicCase, basicPush, caseUserActions, connectorsMock } from '../../containers/mock'; import { CaseConnector } from '../../containers/configure/types'; @@ -36,7 +41,6 @@ const getDefaultProps = (): EditConnectorProps => { isValidConnector: true, onSubmit, userActions: caseUserActions, - userCanCrud: true, }; }; @@ -201,11 +205,9 @@ describe('EditConnector ', () => { }); it('does not allow the connector to be edited when the user does not have write permissions', async () => { - const defaultProps = getDefaultProps(); - const props = { ...defaultProps, userCanCrud: false }; const wrapper = mount( - - + + ); await waitFor(() => diff --git a/x-pack/plugins/cases/public/components/edit_connector/index.tsx b/x-pack/plugins/cases/public/components/edit_connector/index.tsx index 29ab523764b47..0c691460ba007 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx @@ -32,6 +32,7 @@ import { getConnectorById, getConnectorsFormValidators } from '../utils'; import { usePushToService } from '../use_push_to_service'; import { CaseServices } from '../../containers/use_get_case_user_actions'; import { useApplicationCapabilities } from '../../common/lib/kibana'; +import { useCasesContext } from '../cases_context/use_cases_context'; export interface EditConnectorProps { caseData: Case; @@ -48,7 +49,6 @@ export interface EditConnectorProps { onSuccess: () => void ) => void; userActions: CaseUserActions[]; - userCanCrud?: boolean; } const MyFlexGroup = styled(EuiFlexGroup)` @@ -119,8 +119,8 @@ export const EditConnector = React.memo( isValidConnector, onSubmit, userActions, - userCanCrud = true, }: EditConnectorProps) => { + const { permissions } = useCasesContext(); const caseFields = caseData.connector.fields; const selectedConnector = caseData.connector.id; @@ -273,7 +273,6 @@ export const EditConnector = React.memo( connectors, hasDataToPush, onEditClick, - userCanCrud, isValidConnector, }); @@ -289,7 +288,7 @@ export const EditConnector = React.memo(

{i18n.CONNECTORS}

{isLoading && } - {!isLoading && !editConnector && userCanCrud && actionsReadCapabilities && ( + {!isLoading && !editConnector && permissions.all && actionsReadCapabilities && ( {pushButton} diff --git a/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap b/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap index a4103a3e61fe5..41cc919b50200 100644 --- a/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap +++ b/x-pack/plugins/cases/public/components/header_page/__snapshots__/editable_title.test.tsx.snap @@ -41,7 +41,10 @@ exports[`EditableTitle renders 1`] = ` "owner": Array [ "securitySolution", ], - "userCanCrud": true, + "permissions": Object { + "all": true, + "read": true, + }, } } > @@ -49,7 +52,6 @@ exports[`EditableTitle renders 1`] = ` isLoading={false} onSubmit={[MockFunction]} title="Test title" - userCanCrud={true} /> diff --git a/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap index 3848a6db31098..fb8c6d854317a 100644 --- a/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/cases/public/components/header_page/__snapshots__/index.test.tsx.snap @@ -41,7 +41,10 @@ exports[`HeaderPage it renders 1`] = ` "owner": Array [ "securitySolution", ], - "userCanCrud": true, + "permissions": Object { + "all": true, + "read": true, + }, } } > diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx index 111cc4940ac59..f36996c013471 100644 --- a/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.test.tsx @@ -9,7 +9,12 @@ import { shallow } from 'enzyme'; import React from 'react'; import '../../common/mock/match_media'; -import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; +import { + AppMockRenderer, + createAppMockRenderer, + readCasesPermissions, + TestProviders, +} from '../../common/mock'; import { EditableTitle, EditableTitleProps } from './editable_title'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -20,7 +25,6 @@ describe('EditableTitle', () => { title: 'Test title', onSubmit: submitTitle, isLoading: false, - userCanCrud: true, }; beforeEach(() => { @@ -39,8 +43,8 @@ describe('EditableTitle', () => { it('does not show the edit icon when the user does not have edit permissions', () => { const wrapper = mount( - - + + ); diff --git a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx index 0b142ca40a548..c92e1122c53e8 100644 --- a/x-pack/plugins/cases/public/components/header_page/editable_title.tsx +++ b/x-pack/plugins/cases/public/components/header_page/editable_title.tsx @@ -37,19 +37,13 @@ const MySpinner = styled(EuiLoadingSpinner)` `; export interface EditableTitleProps { - userCanCrud: boolean; isLoading: boolean; title: string; onSubmit: (title: string) => void; } -const EditableTitleComponent: React.FC = ({ - userCanCrud = false, - onSubmit, - isLoading, - title, -}) => { - const { releasePhase } = useCasesContext(); +const EditableTitleComponent: React.FC = ({ onSubmit, isLoading, title }) => { + const { releasePhase, permissions } = useCasesContext(); const [editMode, setEditMode] = useState(false); const [errors, setErrors] = useState([]); const [newTitle, setNewTitle] = useState(title); @@ -124,7 +118,7 @@ const EditableTitleComponent: React.FC = ({ ) : ( {isLoading && <MySpinner data-test-subj="editable-title-loading" />} - {!isLoading && userCanCrud && ( + {!isLoading && permissions.all && ( <MyEuiButtonIcon aria-label={i18n.EDIT_TITLE_ARIA(title as string)} iconType="pencil" diff --git a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx index 1fd4a28ddc1b9..eb968834d765e 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { TestProviders } from '../../../common/mock'; +import { readCasesPermissions, TestProviders } from '../../../common/mock'; import { NoCases } from '.'; jest.mock('../../../common/navigation/hooks'); @@ -27,7 +27,7 @@ describe('NoCases', () => { it('displays a message without a link to create a case when the user does not have write permissions', () => { const wrapper = mount( - <TestProviders userCanCrud={false}> + <TestProviders permissions={readCasesPermissions()}> <NoCases /> </TestProviders> ); diff --git a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx index dce685248c4c2..d39dfdbb2c50b 100644 --- a/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/recent_cases/no_cases/index.tsx @@ -13,7 +13,7 @@ import { useCasesContext } from '../../cases_context/use_cases_context'; import { useCreateCaseNavigation } from '../../../common/navigation'; const NoCasesComponent = () => { - const { userCanCrud } = useCasesContext(); + const { permissions } = useCasesContext(); const { getCreateCaseUrl, navigateToCreateCase } = useCreateCaseNavigation(); const navigateToCreateCaseClick = useCallback( @@ -24,7 +24,7 @@ const NoCasesComponent = () => { [navigateToCreateCase] ); - return userCanCrud ? ( + return permissions.all ? ( <> <span>{i18n.NO_CASES}</span> <LinkAnchor diff --git a/x-pack/plugins/cases/public/components/tag_list/index.test.tsx b/x-pack/plugins/cases/public/components/tag_list/index.test.tsx index f795f3ae851ad..46633503dd734 100644 --- a/x-pack/plugins/cases/public/components/tag_list/index.test.tsx +++ b/x-pack/plugins/cases/public/components/tag_list/index.test.tsx @@ -10,7 +10,7 @@ import { mount } from 'enzyme'; import { TagList, TagListProps } from '.'; import { getFormMock } from '../__mock__/form'; -import { TestProviders } from '../../common/mock'; +import { readCasesPermissions, TestProviders } from '../../common/mock'; import { waitFor } from '@testing-library/react'; import { useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib/hooks/use_form'; import { useGetTags } from '../../containers/use_get_tags'; @@ -33,7 +33,6 @@ jest.mock('@elastic/eui', () => { }); const onSubmit = jest.fn(); const defaultProps: TagListProps = { - userCanCrud: true, isLoading: false, onSubmit, tags: [], @@ -109,10 +108,9 @@ describe('TagList ', () => { }); it('does not render when the user does not have write permissions', () => { - const props = { ...defaultProps, userCanCrud: false }; const wrapper = mount( - <TestProviders> - <TagList {...props} /> + <TestProviders permissions={readCasesPermissions()}> + <TagList {...defaultProps} /> </TestProviders> ); expect(wrapper.find(`[data-test-subj="tag-list-edit"]`).exists()).toBeFalsy(); diff --git a/x-pack/plugins/cases/public/components/tag_list/index.tsx b/x-pack/plugins/cases/public/components/tag_list/index.tsx index 2e2f9e783c011..74fb2efcb4fad 100644 --- a/x-pack/plugins/cases/public/components/tag_list/index.tsx +++ b/x-pack/plugins/cases/public/components/tag_list/index.tsx @@ -24,11 +24,11 @@ import { schema } from './schema'; import { useGetTags } from '../../containers/use_get_tags'; import { Tags } from './tags'; +import { useCasesContext } from '../cases_context/use_cases_context'; const CommonUseField = getUseField({ component: Field }); export interface TagListProps { - userCanCrud?: boolean; isLoading: boolean; onSubmit: (a: string[]) => void; tags: string[]; @@ -55,144 +55,143 @@ const ColumnFlexGroup = styled(EuiFlexGroup)` `} `; -export const TagList = React.memo( - ({ userCanCrud = true, isLoading, onSubmit, tags }: TagListProps) => { - const initialState = { tags }; - const { form } = useForm({ - defaultValue: initialState, - options: { stripEmptyFields: false }, - schema, - }); - const { submit } = form; - const [isEditTags, setIsEditTags] = useState(false); +export const TagList = React.memo(({ isLoading, onSubmit, tags }: TagListProps) => { + const { permissions } = useCasesContext(); + const initialState = { tags }; + const { form } = useForm({ + defaultValue: initialState, + options: { stripEmptyFields: false }, + schema, + }); + const { submit } = form; + const [isEditTags, setIsEditTags] = useState(false); - const onSubmitTags = useCallback(async () => { - const { isValid, data: newData } = await submit(); - if (isValid && newData.tags) { - onSubmit(newData.tags); - form.reset({ defaultValue: newData }); - setIsEditTags(false); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [onSubmit, submit]); + const onSubmitTags = useCallback(async () => { + const { isValid, data: newData } = await submit(); + if (isValid && newData.tags) { + onSubmit(newData.tags); + form.reset({ defaultValue: newData }); + setIsEditTags(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onSubmit, submit]); - const { data: tagOptions = [] } = useGetTags(); - const [options, setOptions] = useState( - tagOptions.map((label) => ({ - label, - })) - ); + const { data: tagOptions = [] } = useGetTags(); + const [options, setOptions] = useState( + tagOptions.map((label) => ({ + label, + })) + ); - useEffect( - () => - setOptions( - tagOptions.map((label) => ({ - label, - })) - ), - [tagOptions] - ); - return ( - <EuiText data-test-subj="case-view-tag-list"> - <EuiFlexGroup - alignItems="center" - gutterSize="xs" - justifyContent="spaceBetween" - responsive={false} - > - <EuiFlexItem grow={false}> - <h4>{i18n.TAGS}</h4> + useEffect( + () => + setOptions( + tagOptions.map((label) => ({ + label, + })) + ), + [tagOptions] + ); + return ( + <EuiText data-test-subj="case-view-tag-list"> + <EuiFlexGroup + alignItems="center" + gutterSize="xs" + justifyContent="spaceBetween" + responsive={false} + > + <EuiFlexItem grow={false}> + <h4>{i18n.TAGS}</h4> + </EuiFlexItem> + {isLoading && <EuiLoadingSpinner data-test-subj="tag-list-loading" />} + {!isLoading && permissions.all && ( + <EuiFlexItem data-test-subj="tag-list-edit" grow={false}> + <EuiButtonIcon + data-test-subj="tag-list-edit-button" + aria-label={i18n.EDIT_TAGS_ARIA} + iconType={'pencil'} + onClick={setIsEditTags.bind(null, true)} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + <EuiHorizontalRule margin="xs" /> + <MyFlexGroup gutterSize="none" data-test-subj="case-tags"> + {tags.length === 0 && !isEditTags && <p data-test-subj="no-tags">{i18n.NO_TAGS}</p>} + {!isEditTags && ( + <EuiFlexItem> + <Tags tags={tags} color="hollow" /> </EuiFlexItem> - {isLoading && <EuiLoadingSpinner data-test-subj="tag-list-loading" />} - {!isLoading && userCanCrud && ( - <EuiFlexItem data-test-subj="tag-list-edit" grow={false}> - <EuiButtonIcon - data-test-subj="tag-list-edit-button" - aria-label={i18n.EDIT_TAGS_ARIA} - iconType={'pencil'} - onClick={setIsEditTags.bind(null, true)} - /> + )} + {isEditTags && ( + <ColumnFlexGroup data-test-subj="edit-tags" direction="column"> + <EuiFlexItem> + <Form form={form}> + <CommonUseField + path="tags" + componentProps={{ + idAria: 'caseTags', + 'data-test-subj': 'caseTags', + euiFieldProps: { + fullWidth: true, + placeholder: '', + options, + noSuggestions: false, + }, + }} + /> + <FormDataProvider pathsToWatch="tags"> + {({ tags: anotherTags }) => { + const current: string[] = options.map((opt) => opt.label); + const newOptions = anotherTags.reduce((acc: string[], item: string) => { + if (!acc.includes(item)) { + return [...acc, item]; + } + return acc; + }, current); + if (!isEqual(current, newOptions)) { + setOptions( + newOptions.map((label: string) => ({ + label, + })) + ); + } + return null; + }} + </FormDataProvider> + </Form> </EuiFlexItem> - )} - </EuiFlexGroup> - <EuiHorizontalRule margin="xs" /> - <MyFlexGroup gutterSize="none" data-test-subj="case-tags"> - {tags.length === 0 && !isEditTags && <p data-test-subj="no-tags">{i18n.NO_TAGS}</p>} - {!isEditTags && ( <EuiFlexItem> - <Tags tags={tags} color="hollow" /> + <EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}> + <EuiFlexItem grow={false}> + <EuiButton + color="success" + data-test-subj="edit-tags-submit" + fill + iconType="save" + onClick={onSubmitTags} + size="s" + > + {i18n.SAVE} + </EuiButton> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonEmpty + data-test-subj="edit-tags-cancel" + iconType="cross" + onClick={setIsEditTags.bind(null, false)} + size="s" + > + {i18n.CANCEL} + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> </EuiFlexItem> - )} - {isEditTags && ( - <ColumnFlexGroup data-test-subj="edit-tags" direction="column"> - <EuiFlexItem> - <Form form={form}> - <CommonUseField - path="tags" - componentProps={{ - idAria: 'caseTags', - 'data-test-subj': 'caseTags', - euiFieldProps: { - fullWidth: true, - placeholder: '', - options, - noSuggestions: false, - }, - }} - /> - <FormDataProvider pathsToWatch="tags"> - {({ tags: anotherTags }) => { - const current: string[] = options.map((opt) => opt.label); - const newOptions = anotherTags.reduce((acc: string[], item: string) => { - if (!acc.includes(item)) { - return [...acc, item]; - } - return acc; - }, current); - if (!isEqual(current, newOptions)) { - setOptions( - newOptions.map((label: string) => ({ - label, - })) - ); - } - return null; - }} - </FormDataProvider> - </Form> - </EuiFlexItem> - <EuiFlexItem> - <EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}> - <EuiFlexItem grow={false}> - <EuiButton - color="success" - data-test-subj="edit-tags-submit" - fill - iconType="save" - onClick={onSubmitTags} - size="s" - > - {i18n.SAVE} - </EuiButton> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButtonEmpty - data-test-subj="edit-tags-cancel" - iconType="cross" - onClick={setIsEditTags.bind(null, false)} - size="s" - > - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </ColumnFlexGroup> - )} - </MyFlexGroup> - </EuiText> - ); - } -); + </ColumnFlexGroup> + )} + </MyFlexGroup> + </EuiText> + ); +}); TagList.displayName = 'TagList'; diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx index 15cfefd57ac57..c00ebc7b48045 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.test.tsx @@ -11,7 +11,7 @@ import { render, screen } from '@testing-library/react'; import '../../common/mock/match_media'; import { usePushToService, ReturnUsePushToService, UsePushToService } from '.'; -import { TestProviders } from '../../common/mock'; +import { readCasesPermissions, TestProviders } from '../../common/mock'; import { CaseStatuses, ConnectorTypes } from '../../../common/api'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { basicPush, actionLicenses, connectorsMock } from '../../containers/mock'; @@ -70,7 +70,6 @@ describe('usePushToService', () => { hasDataToPush: true, onEditClick, isValidConnector: true, - userCanCrud: true, }; beforeEach(() => { @@ -281,8 +280,6 @@ describe('usePushToService', () => { }); describe('user does not have write permissions', () => { - const noWriteProps = { ...defaultArgs, userCanCrud: false }; - it('does not display a message when user does not have a premium license', async () => { useFetchActionLicenseMock.mockImplementation(() => ({ isLoading: false, @@ -293,9 +290,11 @@ describe('usePushToService', () => { })); await act(async () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( - () => usePushToService(noWriteProps), + () => usePushToService(defaultArgs), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); @@ -313,9 +312,11 @@ describe('usePushToService', () => { })); await act(async () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( - () => usePushToService(noWriteProps), + () => usePushToService(defaultArgs), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); @@ -328,7 +329,7 @@ describe('usePushToService', () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( () => usePushToService({ - ...noWriteProps, + ...defaultArgs, connectors: [], connector: { id: 'none', @@ -338,7 +339,9 @@ describe('usePushToService', () => { }, }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); @@ -351,7 +354,7 @@ describe('usePushToService', () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( () => usePushToService({ - ...noWriteProps, + ...defaultArgs, connector: { id: 'none', name: 'none', @@ -360,7 +363,9 @@ describe('usePushToService', () => { }, }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); @@ -373,7 +378,7 @@ describe('usePushToService', () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( () => usePushToService({ - ...noWriteProps, + ...defaultArgs, connector: { id: 'not-exist', name: 'not-exist', @@ -383,7 +388,9 @@ describe('usePushToService', () => { isValidConnector: false, }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); @@ -396,7 +403,7 @@ describe('usePushToService', () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( () => usePushToService({ - ...noWriteProps, + ...defaultArgs, connectors: [], connector: { id: 'not-exist', @@ -407,7 +414,9 @@ describe('usePushToService', () => { isValidConnector: false, }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); @@ -420,11 +429,13 @@ describe('usePushToService', () => { const { result, waitForNextUpdate } = renderHook<UsePushToService, ReturnUsePushToService>( () => usePushToService({ - ...noWriteProps, + ...defaultArgs, caseStatus: CaseStatuses.closed, }), { - wrapper: ({ children }) => <TestProviders> {children}</TestProviders>, + wrapper: ({ children }) => ( + <TestProviders permissions={readCasesPermissions()}> {children}</TestProviders> + ), } ); await waitForNextUpdate(); diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx index b2c4e79a35596..253170fdd955c 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/index.tsx @@ -23,6 +23,7 @@ import { CaseServices } from '../../containers/use_get_case_user_actions'; import { ErrorMessage } from './callout/types'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; import { useGetActionLicense } from '../../containers/use_get_action_license'; +import { useCasesContext } from '../cases_context/use_cases_context'; export interface UsePushToService { caseId: string; @@ -33,7 +34,6 @@ export interface UsePushToService { hasDataToPush: boolean; isValidConnector: boolean; onEditClick: () => void; - userCanCrud: boolean; } export interface ReturnUsePushToService { @@ -50,8 +50,8 @@ export const usePushToService = ({ hasDataToPush, isValidConnector, onEditClick, - userCanCrud, }: UsePushToService): ReturnUsePushToService => { + const { permissions } = useCasesContext(); const { isLoading, pushCaseToExternalService } = usePostPushToService(); const { isLoading: loadingLicense, data: actionLicense = null } = useGetActionLicense(); @@ -76,7 +76,7 @@ export const usePushToService = ({ // these message require that the user do some sort of write action as a result of the message, readonly users won't // be able to perform such an action so let's not display the error to the user in that situation - if (!userCanCrud) { + if (!permissions.all) { return errors; } @@ -114,7 +114,7 @@ export const usePushToService = ({ return errors; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionLicense, caseStatus, connectors.length, connector, loadingLicense, userCanCrud]); + }, [actionLicense, caseStatus, connectors.length, connector, loadingLicense, permissions.all]); const pushToServiceButton = useMemo( () => ( @@ -126,7 +126,7 @@ export const usePushToService = ({ isLoading || loadingLicense || errorsMsg.length > 0 || - !userCanCrud || + !permissions.all || !isValidConnector || !hasDataToPush } @@ -146,29 +146,26 @@ export const usePushToService = ({ hasDataToPush, isLoading, loadingLicense, - userCanCrud, + permissions.all, isValidConnector, ] ); - const objToReturn = useMemo( - () => ({ - pushButton: - errorsMsg.length > 0 || !hasDataToPush ? ( - <EuiToolTip - position="top" - title={ - errorsMsg.length > 0 ? errorsMsg[0].title : i18n.PUSH_LOCKED_TITLE(connector.name) - } - content={ - <p>{errorsMsg.length > 0 ? errorsMsg[0].description : i18n.PUSH_LOCKED_DESC}</p> - } - > - {pushToServiceButton} - </EuiToolTip> - ) : ( - <>{pushToServiceButton}</> - ), + const objToReturn = useMemo(() => { + const hidePushButton = errorsMsg.length > 0 || !hasDataToPush || !permissions.all; + + return { + pushButton: hidePushButton ? ( + <EuiToolTip + position="top" + title={errorsMsg.length > 0 ? errorsMsg[0].title : i18n.PUSH_LOCKED_TITLE(connector.name)} + content={<p>{errorsMsg.length > 0 ? errorsMsg[0].description : i18n.PUSH_LOCKED_DESC}</p>} + > + {pushToServiceButton} + </EuiToolTip> + ) : ( + <>{pushToServiceButton}</> + ), pushCallouts: errorsMsg.length > 0 ? ( <CaseCallOut @@ -178,17 +175,17 @@ export const usePushToService = ({ onEditClick={onEditClick} /> ) : null, - }), - [ - connector.name, - connectors.length, - errorsMsg, - hasDataToPush, - hasLicenseError, - onEditClick, - pushToServiceButton, - ] - ); + }; + }, [ + connector.name, + connectors.length, + errorsMsg, + hasDataToPush, + hasLicenseError, + onEditClick, + pushToServiceButton, + permissions.all, + ]); return objToReturn; }; diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx index 589efe48cd188..0dfd5876cea6b 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/comment.tsx @@ -42,7 +42,6 @@ const getCreateCommentUserAction = ({ caseData, externalReferenceAttachmentTypeRegistry, comment, - userCanCrud, commentRefs, manageMarkdownEditIds, selectedOutlineCommentId, @@ -68,7 +67,6 @@ const getCreateCommentUserAction = ({ case CommentType.user: const userBuilder = createUserAttachmentUserActionBuilder({ comment, - userCanCrud, outlined: comment.id === selectedOutlineCommentId, isEdit: manageMarkdownEditIds.includes(comment.id), commentRefs, @@ -116,7 +114,6 @@ export const createCommentUserActionBuilder: UserActionBuilder = ({ caseData, externalReferenceAttachmentTypeRegistry, userAction, - userCanCrud, commentRefs, manageMarkdownEditIds, selectedOutlineCommentId, @@ -152,7 +149,6 @@ export const createCommentUserActionBuilder: UserActionBuilder = ({ userAction: commentUserAction, externalReferenceAttachmentTypeRegistry, comment, - userCanCrud, commentRefs, manageMarkdownEditIds, selectedOutlineCommentId, diff --git a/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx b/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx index 6c4c96a95bc46..398f58da97b9c 100644 --- a/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/comment/user.tsx @@ -20,7 +20,6 @@ import { UserActionBuilderArgs, UserActionBuilder } from '../types'; type BuilderArgs = Pick< UserActionBuilderArgs, - | 'userCanCrud' | 'handleManageMarkdownEditId' | 'handleSaveComment' | 'handleManageQuote' @@ -35,7 +34,6 @@ type BuilderArgs = Pick< export const createUserAttachmentUserActionBuilder = ({ comment, - userCanCrud, outlined, isEdit, isLoading, @@ -95,7 +93,6 @@ export const createUserAttachmentUserActionBuilder = ({ onEdit={handleManageMarkdownEditId.bind(null, comment.id)} onQuote={handleManageQuote.bind(null, comment.comment)} onDelete={handleDeleteComment.bind(null, comment.id)} - userCanCrud={userCanCrud} /> ), }, diff --git a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx index 74f5205578a1d..bba8303149ae9 100644 --- a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.test.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { UserActionContentToolbar, UserActionContentToolbarProps } from './content_toolbar'; +import { TestProviders } from '../../common/mock'; jest.mock('../../common/navigation/hooks'); jest.mock('../../common/lib/kibana'); @@ -17,7 +18,6 @@ const props: UserActionContentToolbarProps = { id: '1', editLabel: 'edit', quoteLabel: 'quote', - userCanCrud: true, isLoading: false, onEdit: jest.fn(), onQuote: jest.fn(), @@ -27,7 +27,11 @@ describe('UserActionContentToolbar ', () => { let wrapper: ReactWrapper; beforeAll(() => { - wrapper = mount(<UserActionContentToolbar {...props} />); + wrapper = mount( + <TestProviders> + <UserActionContentToolbar {...props} /> + </TestProviders> + ); }); it('it renders', async () => { diff --git a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx index bea47933dccd6..e23a4efa2f0a2 100644 --- a/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/content_toolbar.tsx @@ -22,7 +22,6 @@ export interface UserActionContentToolbarProps { onEdit: (id: string) => void; onQuote: (id: string) => void; onDelete?: (id: string) => void; - userCanCrud: boolean; } const UserActionContentToolbarComponent = ({ @@ -36,7 +35,6 @@ const UserActionContentToolbarComponent = ({ onEdit, onQuote, onDelete, - userCanCrud, }: UserActionContentToolbarProps) => ( <EuiFlexGroup responsive={false} alignItems="center"> <EuiFlexItem grow={false}> @@ -53,7 +51,6 @@ const UserActionContentToolbarComponent = ({ onEdit={onEdit} onQuote={onQuote} onDelete={onDelete} - userCanCrud={userCanCrud} commentMarkdown={commentMarkdown} /> </EuiFlexItem> diff --git a/x-pack/plugins/cases/public/components/user_actions/description.tsx b/x-pack/plugins/cases/public/components/user_actions/description.tsx index eae2bd3d1258e..ef8f26bd0e87b 100644 --- a/x-pack/plugins/cases/public/components/user_actions/description.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/description.tsx @@ -27,7 +27,6 @@ type GetDescriptionUserActionArgs = Pick< | 'caseData' | 'commentRefs' | 'manageMarkdownEditIds' - | 'userCanCrud' | 'handleManageMarkdownEditId' | 'handleManageQuote' > & @@ -38,7 +37,6 @@ export const getDescriptionUserAction = ({ commentRefs, manageMarkdownEditIds, isLoadingDescription, - userCanCrud, onUpdateField, handleManageMarkdownEditId, handleManageQuote, @@ -85,7 +83,6 @@ export const getDescriptionUserAction = ({ isLoading={isLoadingDescription} onEdit={handleManageMarkdownEditId.bind(null, DESCRIPTION_ID)} onQuote={handleManageQuote.bind(null, caseData.description)} - userCanCrud={userCanCrud} /> ), }; diff --git a/x-pack/plugins/cases/public/components/user_actions/index.test.tsx b/x-pack/plugins/cases/public/components/user_actions/index.test.tsx index b6122ebec4016..9a971552f5ec3 100644 --- a/x-pack/plugins/cases/public/components/user_actions/index.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/index.test.tsx @@ -44,7 +44,6 @@ const defaultProps = { selectedAlertPatterns: ['some-test-pattern'], statusActionButton: null, updateCase, - userCanCrud: true, useFetchAlertData: (): [boolean, Record<string, unknown>] => [ false, { 'some-id': { _id: 'some-id' } }, diff --git a/x-pack/plugins/cases/public/components/user_actions/index.tsx b/x-pack/plugins/cases/public/components/user_actions/index.tsx index c8d038bb2b0f5..1c456f90a71e7 100644 --- a/x-pack/plugins/cases/public/components/user_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/index.tsx @@ -91,7 +91,6 @@ export const UserActions = React.memo( onUpdateField, statusActionButton, useFetchAlertData, - userCanCrud, }: UserActionTreeProps) => { const { detailName: caseId, commentId } = useCaseViewParams(); const [initLoading, setInitLoading] = useState(true); @@ -123,7 +122,6 @@ export const UserActions = React.memo( <AddComment id={NEW_COMMENT_ID} caseId={caseId} - userCanCrud={userCanCrud} ref={(element) => (commentRefs.current[NEW_COMMENT_ID] = element)} onCommentPosted={handleUpdate} onCommentSaving={handleManageMarkdownEditId.bind(null, NEW_COMMENT_ID)} @@ -131,14 +129,7 @@ export const UserActions = React.memo( statusActionButton={statusActionButton} /> ), - [ - caseId, - userCanCrud, - handleUpdate, - handleManageMarkdownEditId, - statusActionButton, - commentRefs, - ] + [caseId, handleUpdate, handleManageMarkdownEditId, statusActionButton, commentRefs] ); useEffect(() => { @@ -157,7 +148,6 @@ export const UserActions = React.memo( commentRefs, manageMarkdownEditIds, isLoadingDescription, - userCanCrud, onUpdateField, handleManageMarkdownEditId, handleManageQuote, @@ -167,7 +157,6 @@ export const UserActions = React.memo( commentRefs, manageMarkdownEditIds, isLoadingDescription, - userCanCrud, onUpdateField, handleManageMarkdownEditId, handleManageQuote, @@ -195,7 +184,6 @@ export const UserActions = React.memo( caseServices, comments: caseData.comments, index, - userCanCrud, commentRefs, manageMarkdownEditIds, selectedOutlineCommentId, @@ -222,7 +210,6 @@ export const UserActions = React.memo( descriptionCommentListObj, caseData, caseServices, - userCanCrud, commentRefs, manageMarkdownEditIds, selectedOutlineCommentId, @@ -241,7 +228,9 @@ export const UserActions = React.memo( ] ); - const bottomActions = userCanCrud + const { permissions } = useCasesContext(); + + const bottomActions = permissions.all ? [ { username: ( diff --git a/x-pack/plugins/cases/public/components/user_actions/mock.ts b/x-pack/plugins/cases/public/components/user_actions/mock.ts index 7d6bbd8b58c59..777241aca71eb 100644 --- a/x-pack/plugins/cases/public/components/user_actions/mock.ts +++ b/x-pack/plugins/cases/public/components/user_actions/mock.ts @@ -67,7 +67,6 @@ export const getMockBuilderArgs = (): UserActionBuilderArgs => { caseServices, index: 0, alertData, - userCanCrud: true, commentRefs, manageMarkdownEditIds: [], selectedOutlineCommentId: '', diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx index 0084301183e68..e9319169b953c 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions.test.tsx @@ -10,6 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { UserActionPropertyActions } from './property_actions'; import { render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { TestProviders } from '../../common/mock'; jest.mock('../../common/lib/kibana'); @@ -24,14 +25,17 @@ const props = { isLoading: false, onEdit, onQuote, - userCanCrud: true, }; describe('UserActionPropertyActions ', () => { let wrapper: ReactWrapper; beforeAll(() => { - wrapper = mount(<UserActionPropertyActions {...props} />); + wrapper = mount( + <TestProviders> + <UserActionPropertyActions {...props} /> + </TestProviders> + ); }); beforeEach(() => { @@ -65,7 +69,11 @@ describe('UserActionPropertyActions ', () => { }); it('shows the spinner when loading', async () => { - wrapper = mount(<UserActionPropertyActions {...props} isLoading={true} />); + wrapper = mount( + <TestProviders> + <UserActionPropertyActions {...props} isLoading={true} /> + </TestProviders> + ); expect( wrapper.find('[data-test-subj="user-action-title-loading"]').first().exists() ).toBeTruthy(); @@ -82,14 +90,22 @@ describe('UserActionPropertyActions ', () => { deleteConfirmlabel: 'confirm delete me', }; it('shows the delete button', () => { - const renderResult = render(<UserActionPropertyActions {...deleteProps} />); + const renderResult = render( + <TestProviders> + <UserActionPropertyActions {...deleteProps} /> + </TestProviders> + ); userEvent.click(renderResult.getByTestId('property-actions-ellipses')); expect(renderResult.getByTestId('property-actions-trash')).toBeTruthy(); }); it('shows a confirm dialog when the delete button is clicked', () => { - const renderResult = render(<UserActionPropertyActions {...deleteProps} />); + const renderResult = render( + <TestProviders> + <UserActionPropertyActions {...deleteProps} /> + </TestProviders> + ); userEvent.click(renderResult.getByTestId('property-actions-ellipses')); userEvent.click(renderResult.getByTestId('property-actions-trash')); @@ -98,7 +114,11 @@ describe('UserActionPropertyActions ', () => { }); it('closes the confirm dialog when the cancel button is clicked', () => { - const renderResult = render(<UserActionPropertyActions {...deleteProps} />); + const renderResult = render( + <TestProviders> + <UserActionPropertyActions {...deleteProps} /> + </TestProviders> + ); userEvent.click(renderResult.getByTestId('property-actions-ellipses')); userEvent.click(renderResult.getByTestId('property-actions-trash')); @@ -109,7 +129,11 @@ describe('UserActionPropertyActions ', () => { }); it('calls onDelete when the confirm is pressed', () => { - const renderResult = render(<UserActionPropertyActions {...deleteProps} />); + const renderResult = render( + <TestProviders> + <UserActionPropertyActions {...deleteProps} /> + </TestProviders> + ); userEvent.click(renderResult.getByTestId('property-actions-ellipses')); userEvent.click(renderResult.getByTestId('property-actions-trash')); diff --git a/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx b/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx index 898ef87a0b2b9..502e69a9d1903 100644 --- a/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/property_actions.tsx @@ -11,6 +11,7 @@ import { EuiConfirmModal, EuiLoadingSpinner } from '@elastic/eui'; import { PropertyActions } from '../property_actions'; import { useLensOpenVisualization } from '../markdown_editor/plugins/lens/use_lens_open_visualization'; import { CANCEL_BUTTON, CONFIRM_BUTTON } from './translations'; +import { useCasesContext } from '../cases_context/use_cases_context'; interface UserActionPropertyActionsProps { id: string; @@ -22,7 +23,6 @@ interface UserActionPropertyActionsProps { onEdit: (id: string) => void; onDelete?: (id: string) => void; onQuote: (id: string) => void; - userCanCrud: boolean; commentMarkdown: string; } @@ -36,9 +36,9 @@ const UserActionPropertyActionsComponent = ({ onEdit, onDelete, onQuote, - userCanCrud, commentMarkdown, }: UserActionPropertyActionsProps) => { + const { permissions } = useCasesContext(); const { canUseEditor, actionConfig } = useLensOpenVisualization({ comment: commentMarkdown }); const onEditClick = useCallback(() => onEdit(id), [id, onEdit]); const onQuoteClick = useCallback(() => onQuote(id), [id, onQuote]); @@ -62,7 +62,7 @@ const UserActionPropertyActionsComponent = ({ const propertyActions = useMemo( () => [ - userCanCrud + permissions.all ? [ { iconType: 'pencil', @@ -88,7 +88,7 @@ const UserActionPropertyActionsComponent = ({ canUseEditor && actionConfig ? [actionConfig] : [], ].flat(), [ - userCanCrud, + permissions.all, editLabel, onEditClick, deleteLabel, diff --git a/x-pack/plugins/cases/public/components/user_actions/types.ts b/x-pack/plugins/cases/public/components/user_actions/types.ts index 2fdcc64be851e..a834328075a09 100644 --- a/x-pack/plugins/cases/public/components/user_actions/types.ts +++ b/x-pack/plugins/cases/public/components/user_actions/types.ts @@ -30,7 +30,6 @@ export interface UserActionTreeProps { onUpdateField: ({ key, value, onSuccess, onError }: OnUpdateFields) => void; statusActionButton: JSX.Element | null; useFetchAlertData: UseFetchAlertData; - userCanCrud: boolean; } type UnsupportedUserActionTypes = typeof UNSUPPORTED_ACTION_TYPES[number]; @@ -43,7 +42,6 @@ export interface UserActionBuilderArgs { caseServices: CaseServices; comments: Comment[]; index: number; - userCanCrud: boolean; commentRefs: React.MutableRefObject< Record<string, AddCommentRefObject | UserActionMarkdownRefObject | null | undefined> >; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/about_panel.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/about_panel.tsx index 6555b95c3f470..a48a2365486fe 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/about_panel.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/about_panel.tsx @@ -26,9 +26,10 @@ import { WelcomeContent } from './welcome_content'; interface Props { onFilePickerChange(files: FileList | null): void; + hasPermissionToImport: boolean; } -export const AboutPanel: FC<Props> = ({ onFilePickerChange }) => { +export const AboutPanel: FC<Props> = ({ onFilePickerChange, hasPermissionToImport }) => { return ( <EuiPageBody paddingSize="none" @@ -39,7 +40,7 @@ export const AboutPanel: FC<Props> = ({ onFilePickerChange }) => { <EuiPageContent hasShadow={false} hasBorder> <EuiFlexGroup gutterSize="xl"> <EuiFlexItem grow={true}> - <WelcomeContent /> + <WelcomeContent hasPermissionToImport={hasPermissionToImport} /> <EuiHorizontalRule margin="l" /> diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx index 3a162a805f9ae..b2937a6189b0a 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx @@ -12,7 +12,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiText, EuiTitle } from import { useDataVisualizerKibana } from '../../../kibana_context'; -export const WelcomeContent: FC = () => { +interface Props { + hasPermissionToImport: boolean; +} + +export const WelcomeContent: FC<Props> = ({ hasPermissionToImport }) => { const { services: { fileUpload: { getMaxBytesFormatted }, @@ -37,10 +41,17 @@ export const WelcomeContent: FC = () => { <EuiSpacer size="s" /> <EuiText> <p> - <FormattedMessage - id="xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileDescription" - defaultMessage="Upload your file, analyze its data, and optionally import the data into an Elasticsearch index." - /> + {hasPermissionToImport ? ( + <FormattedMessage + id="xpack.dataVisualizer.file.welcomeContent.visualizeAndImportDataFromLogFileDescription" + defaultMessage="Upload your file, analyze its data, and optionally import the data into an Elasticsearch index." + /> + ) : ( + <FormattedMessage + id="xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileDescription" + defaultMessage="Upload your file and analyze its data." + /> + )} </p> </EuiText> <EuiSpacer size="s" /> diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js index c34a6e0b9cbd9..b9766e65a3555 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js @@ -296,7 +296,12 @@ export class FileDataVisualizerView extends Component { <div> {mode === MODE.READ && ( <> - {!loading && !loaded && <AboutPanel onFilePickerChange={this.onFilePickerChange} />} + {!loading && !loaded && ( + <AboutPanel + onFilePickerChange={this.onFilePickerChange} + hasPermissionToImport={hasPermissionToImport} + /> + )} {loading && <LoadingPanel />} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx index 7c8abaa1377b9..8c6cc93ccf843 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/documents/organic_documents.tsx @@ -10,13 +10,13 @@ import React from 'react'; import { useValues, useActions } from 'kea'; import { EuiLoadingContent, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { SearchResult } from '@elastic/search-ui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { LeafIcon } from '../../../../../shared/icons'; import { DataPanel } from '../../../data_panel'; -import { Result } from '../../../result/types'; import { PROMOTE_DOCUMENT_ACTION, HIDE_DOCUMENT_ACTION } from '../../constants'; import { CurationLogic } from '../curation_logic'; @@ -47,7 +47,7 @@ export const OrganicDocuments: React.FC = () => { > {hasDocuments ? ( <EuiFlexGroup direction="column" gutterSize="s"> - {documents.map((document: Result, index) => ( + {documents.map((document: SearchResult, index) => ( <EuiFlexItem key={index}> <CurationResult result={document} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx index 8e55e775218bf..ce97bf468d4e3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/curation_result.tsx @@ -10,14 +10,16 @@ import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd'; import { useValues } from 'kea'; +import type { SearchResult } from '@elastic/search-ui'; + import { EngineLogic } from '../../../engine'; import { Result } from '../../../result'; -import { Result as ResultType, ResultAction } from '../../../result/types'; +import { ResultAction } from '../../../result/types'; interface Props { actions: ResultAction[]; dragHandleProps?: DraggableProvidedDragHandleProps; - result: ResultType; + result: SearchResult; index?: number; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts index 199a6b2ff5040..5c56eb934b150 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/results/utils.ts @@ -5,7 +5,9 @@ * 2.0. */ -import { Result, ResultMeta } from '../../../result/types'; +import type { SearchResult } from '@elastic/search-ui'; + +import { ResultMeta } from '../../../result/types'; import { CurationResult } from '../../types'; /** @@ -26,8 +28,8 @@ const mergeMetas = (partialMeta: ResultMeta, secondPartialMeta: ResultMeta): Res }; }; -export const convertToResultFormat = (document: CurationResult): Result => { - const result = {} as Result; +export const convertToResultFormat = (document: CurationResult): SearchResult => { + const result = {} as SearchResult; // Convert `key: 'value'` into `key: { raw: 'value' }` Object.entries(document).forEach(([key, value]) => { @@ -49,7 +51,7 @@ export const convertToResultFormat = (document: CurationResult): Result => { return result; }; -export const convertIdToMeta = (id: string): Result['_meta'] => { +export const convertIdToMeta = (id: string): SearchResult['_meta'] => { const splitId = id.split('|'); const isMetaEngine = splitId.length > 1; @@ -58,7 +60,7 @@ export const convertIdToMeta = (id: string): Result['_meta'] => { engine: splitId[0], id: splitId[1], } - : ({ id } as Result['_meta']); + : ({ id } as SearchResult['_meta']); // Note: We're casting this as _meta even though `engine` is missing, // since for source engines the engine shouldn't matter / be displayed, // but if needed we could likely populate this from EngineLogic.values diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts index fea3dac75e3b8..73aefbdfd205d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/types.ts @@ -5,8 +5,10 @@ * 2.0. */ +import type { SearchResult } from '@elastic/search-ui'; + import { Meta } from '../../../../../common/types'; -import { Result, ResultMeta } from '../result/types'; +import { ResultMeta } from '../result/types'; export interface CurationSuggestion { query: string; @@ -32,7 +34,7 @@ export interface Curation { queries: string[]; promoted: CurationResult[]; hidden: CurationResult[]; - organic?: Result[]; // this field is missing if there are 0 results + organic?: SearchResult[]; // this field is missing if there are 0 results suggestion?: CurationSuggestion; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx index de3160b1d0e2f..142d077501af2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_result_panel.tsx @@ -17,17 +17,18 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import type { SearchResult } from '@elastic/search-ui'; + import { i18n } from '@kbn/i18n'; import { EngineLogic } from '../../../engine'; import { Result } from '../../../result'; -import { Result as ResultType } from '../../../result/types'; import './curation_result_panel.scss'; interface Props { variant: 'current' | 'promoted' | 'suggested' | 'hidden'; - results: ResultType[]; + results: SearchResult[]; } export const CurationResultPanel: React.FC<Props> = ({ variant, results }) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx index 4e344d8cc2a39..bed16d609ef15 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion.tsx @@ -19,6 +19,8 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; +import type { SearchResult } from '@elastic/search-ui'; + import { i18n } from '@kbn/i18n'; import { LeafIcon } from '../../../../../shared/icons'; @@ -26,7 +28,6 @@ import { useDecodedParams } from '../../../../utils/encode_path_params'; import { EngineLogic } from '../../../engine'; import { AppSearchPageTemplate } from '../../../layout'; import { Result } from '../../../result'; -import { Result as ResultType } from '../../../result/types'; import { convertToResultFormat } from '../../curation/results'; import { getCurationsBreadcrumbs } from '../../utils'; @@ -149,7 +150,7 @@ export const CurationSuggestion: React.FC = () => { gutterSize="s" data-test-subj="currentOrganicResults" > - {currentOrganicResults.map((result: ResultType, index) => ( + {currentOrganicResults.map((result: SearchResult, index) => ( <EuiFlexItem grow={false} key={result.id.raw}> <Result result={result} @@ -169,7 +170,7 @@ export const CurationSuggestion: React.FC = () => { gutterSize="s" data-test-subj="proposedOrganicResults" > - {proposedOrganicResults.map((result: ResultType, index) => ( + {proposedOrganicResults.map((result: SearchResult, index) => ( <EuiFlexItem grow={false} key={result.id.raw}> <Result result={result} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts index 472a37e158062..6cd55be21f984 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { APIConnector } from '@elastic/search-ui'; + import { SchemaType } from '../../../../shared/schema/types'; import { buildSearchUIConfig } from './build_search_ui_config'; @@ -21,7 +23,7 @@ describe('buildSearchUIConfig', () => { sortFields: [], }; - const config = buildSearchUIConfig(connector, schema, fields); + const config = buildSearchUIConfig(connector as APIConnector, schema, fields); expect(config).toEqual({ alwaysSearchOnInitialLoad: true, apiConnector: connector, @@ -44,10 +46,6 @@ describe('buildSearchUIConfig', () => { result_fields: { bar: { raw: {}, - snippet: { - fallback: true, - size: 300, - }, }, foo: { raw: {}, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts index 9c06527162b81..1edcc3cdfc27a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts @@ -5,23 +5,28 @@ * 2.0. */ +import type { APIConnector, SortDirection } from '@elastic/search-ui'; + import { Schema } from '../../../../shared/schema/types'; import { Fields } from './types'; export const buildSearchUIConfig = ( - apiConnector: object, + apiConnector: APIConnector, schema: Schema, fields: Fields, - initialState = { sortDirection: 'desc', sortField: 'id' } + initialState = { sortDirection: 'desc' as SortDirection, sortField: 'id' } ) => { - const facets = fields.filterFields.reduce( - (facetsConfig, fieldName) => ({ + const facets = fields.filterFields.reduce((facetsConfig, fieldName) => { + // Geolocation fields do not support value facets https://www.elastic.co/guide/en/app-search/current/facets.html + if (schema[fieldName] === 'geolocation') { + return facetsConfig; + } + return { ...facetsConfig, [fieldName]: { type: 'value', size: 30 }, - }), - {} - ); + }; + }, {}); return { alwaysSearchOnInitialLoad: true, @@ -32,13 +37,20 @@ export const buildSearchUIConfig = ( disjunctiveFacets: fields.filterFields, facets, result_fields: Object.keys(schema).reduce((acc: { [key: string]: object }, key: string) => { - acc[key] = { - snippet: { - size: 300, - fallback: true, - }, - raw: {}, - }; + if (schema[key] === 'text') { + // Only text fields support snippets + acc[key] = { + snippet: { + size: 300, + fallback: true, + }, + raw: {}, + }; + } else { + acc[key] = { + raw: {}, + }; + } return acc; }, {}), }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx index 028a9af21311f..f1bf6289929a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.test.tsx @@ -9,8 +9,8 @@ const mockAction = jest.fn(); let mockSubcription: (state: object) => void; const mockDriver = { - state: { foo: 'foo' }, - actions: { bar: mockAction }, + state: { searchTerm: 'foo' }, + actions: { setSearchTerm: mockAction }, subscribeToStateChanges: jest.fn().mockImplementation((fn) => { mockSubcription = fn; }), @@ -34,8 +34,8 @@ import { useSearchContextState, useSearchContextActions } from './hooks'; describe('hooks', () => { describe('useSearchContextState', () => { const TestComponent = () => { - const { foo } = useSearchContextState(); - return <div>{foo}</div>; + const { searchTerm } = useSearchContextState(); + return <div>{searchTerm}</div>; }; let wrapper: ReactWrapper; @@ -49,7 +49,7 @@ describe('hooks', () => { it('subscribes to state changes', () => { act(() => { - mockSubcription({ foo: 'bar' }); + mockSubcription({ searchTerm: 'bar' }); }); expect(wrapper.text()).toEqual('bar'); @@ -65,8 +65,8 @@ describe('hooks', () => { describe('useSearchContextActions', () => { it('exposes actions', () => { const TestComponent = () => { - const { bar } = useSearchContextActions(); - bar(); + const { setSearchTerm } = useSearchContextActions(); + setSearchTerm('bar'); return null; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts index 7fb05c0aaba69..7b1029970a444 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/hooks.ts @@ -7,19 +7,20 @@ import { useContext, useEffect, useState } from 'react'; -// @ts-expect-error types are not available for this package yet import { SearchContext } from '@elastic/react-search-ui'; +import type { SearchState } from '@elastic/search-ui'; export const useSearchContextState = () => { const { driver } = useContext(SearchContext); const [state, setState] = useState(driver.state); useEffect(() => { - driver.subscribeToStateChanges((newState: object) => { + const subscription = (newState: SearchState) => { setState(newState); - }); + }; + driver.subscribeToStateChanges(subscription); return () => { - driver.unsubscribeToStateChanges(); + driver.unsubscribeToStateChanges(subscription); }; }, [state]); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx index b55163ca9843a..cc438abd9ba94 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.test.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -// @ts-expect-error types are not available for this package yet import { Paging, ResultsPerPage } from '@elastic/react-search-ui'; import { Pagination } from './pagination'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx index d81b056842642..53b0e22553d52 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/pagination.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -// @ts-expect-error types are not available for this package yet import { Paging, ResultsPerPage } from '@elastic/react-search-ui'; import { PagingView, ResultsPerPageView } from './views'; @@ -20,6 +19,7 @@ export const Pagination: React.FC<{ 'aria-label': string }> = ({ 'aria-label': a className="documentsSearchExperience__pagingInfo" > <EuiFlexItem> + {/* @ts-ignore */} <Paging view={PagingView} aria-label={ariaLabel} /> </EuiFlexItem> <EuiFlexItem grow={false}> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx index 2871e4f96015f..1a6a2abba0904 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.test.tsx @@ -12,7 +12,6 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -// @ts-expect-error types are not available for this package yet import { SearchProvider, Facet } from '@elastic/react-search-ui'; jest.mock('../../../../shared/use_local_storage', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx index fc54b9cdf300e..397bcd2e14f83 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx @@ -10,9 +10,8 @@ import React, { useState } from 'react'; import { useValues } from 'kea'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -// @ts-expect-error types are not available for this package yet; import { SearchProvider, SearchBox, Sorting, Facet } from '@elastic/react-search-ui'; -// @ts-expect-error types are not available for this package yet +import type { SortDirection } from '@elastic/search-ui'; import AppSearchAPIConnector from '@elastic/search-ui-app-search-connector'; import { i18n } from '@kbn/i18n'; @@ -88,15 +87,15 @@ export const SearchExperience: React.FC = () => { const connector = new AppSearchAPIConnector({ cacheResponses: false, endpointBase, - engineName: engine.name, + engineName: engine.name as string, additionalHeaders: { 'kbn-xsrf': true, }, - }); + } as ConstructorParameters<typeof AppSearchAPIConnector>[0]); const initialState = { sortField: sortOptions[0].value, - sortDirection: 'desc', + sortDirection: 'desc' as SortDirection, }; const searchProviderConfig = buildSearchUIConfig( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx index e573502d76b9f..16a18a71981a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx @@ -12,7 +12,6 @@ import React from 'react'; import { shallow } from 'enzyme'; -// @ts-expect-error types are not available for this package yet import { Results } from '@elastic/react-search-ui'; import { Loading } from '../../../../shared/loading'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx index b03aa6a64c7d9..2dbafaacb1e93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx @@ -10,13 +10,13 @@ import React from 'react'; import { useValues } from 'kea'; import { EuiFlexGroup, EuiSpacer, EuiEmptyPrompt } from '@elastic/eui'; -// @ts-expect-error types are not available for this package yet + import { Results } from '@elastic/react-search-ui'; +import type { SearchResult } from '@elastic/search-ui'; import { i18n } from '@kbn/i18n'; import { Loading } from '../../../../shared/loading'; import { EngineLogic } from '../../engine'; -import { Result } from '../../result/types'; import { useSearchContextState } from './hooks'; import { Pagination } from './pagination'; @@ -43,7 +43,7 @@ export const SearchExperienceContent: React.FC = () => { <EuiSpacer /> <Results titleField="id" - resultView={({ result }: { result: Result }) => { + resultView={({ result }: { result: SearchResult }) => { return ( <ResultView result={result} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx index 0f357a0b894fa..85984054ebd21 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.test.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { shallow } from 'enzyme'; +import type { FacetValue } from '@elastic/search-ui'; + import { MultiCheckboxFacetsView } from './multi_checkbox_facets_view'; describe('MultiCheckboxFacetsView', () => { @@ -23,7 +25,7 @@ describe('MultiCheckboxFacetsView', () => { value: 'value2', selected: false, }, - ], + ] as FacetValue[], showMore: true, onMoreClick: jest.fn(), onRemove: jest.fn(), @@ -65,7 +67,7 @@ describe('MultiCheckboxFacetsView', () => { value: 'value2', selected: true, }, - ], + ] as FacetValue[], }} /> ); @@ -87,7 +89,7 @@ describe('MultiCheckboxFacetsView', () => { value: '', selected: false, }, - ], + ] as FacetValue[], }} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx index 98770c7a792b4..af650ce7a0146 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/multi_checkbox_facets_view.tsx @@ -14,20 +14,16 @@ import { EuiButtonEmpty, EuiSpacer, } from '@elastic/eui'; +import type { FieldValue, FacetValue } from '@elastic/search-ui'; import { i18n } from '@kbn/i18n'; -interface Option { - value: string; - selected: boolean; -} - interface Props { label: string; - options: Option[]; + options: FacetValue[]; showMore: boolean; onMoreClick(): void; - onRemove(id: string): void; - onSelect(id: string): void; + onRemove(value: FieldValue): void; + onSelect(value: FieldValue): void; } const getIndexFromId = (id: string) => parseInt(id.split('_')[1], 10); @@ -42,7 +38,7 @@ export const MultiCheckboxFacetsView: React.FC<Props> = ({ }) => { const getId = htmlIdGenerator(); - const optionToCheckBoxGroupOption = (option: Option, index: number) => ({ + const optionToCheckBoxGroupOption = (option: FacetValue, index: number) => ({ id: getId(String(index)), label: option.value || @@ -56,7 +52,7 @@ export const MultiCheckboxFacetsView: React.FC<Props> = ({ const optionToSelectedMapReducer = ( selectedMap: { [name: string]: boolean }, - option: Option, + option: FacetValue, index: number ) => { if (option.selected) { @@ -72,10 +68,10 @@ export const MultiCheckboxFacetsView: React.FC<Props> = ({ const index = getIndexFromId(checkboxId); const option = options[index]; if (option.selected) { - onRemove(option.value); + onRemove(option.value as FieldValue); return; } - onSelect(option.value); + onSelect(option.value as FieldValue); }; return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx index 1ae194c7bab66..c79ea36ba100a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/paging_view.tsx @@ -10,14 +10,14 @@ import React from 'react'; import { EuiPagination } from '@elastic/eui'; interface Props { - current: number; + current?: number; totalPages: number; onChange(pageNumber: number): void; 'aria-label': string; } export const PagingView: React.FC<Props> = ({ - current, + current = 1, onChange, totalPages, 'aria-label': ariaLabel, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx index 45dafe385f737..930b60f22a947 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx @@ -7,12 +7,13 @@ import React from 'react'; +import type { SearchResult } from '@elastic/search-ui'; + import { Schema } from '../../../../../shared/schema/types'; import { Result } from '../../../result/result'; -import { Result as ResultType } from '../../../result/types'; export interface Props { - result: ResultType; + result: SearchResult; schemaForTypeHighlights?: Schema; isMetaEngine: boolean; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx index 5056d56d1f3d0..63050ca1d79ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/results_per_page_view.tsx @@ -16,12 +16,12 @@ const wrapResultsPerPageOptionForEuiSelect: (option: number) => EuiSelectOption }); interface Props { - options: number[]; - value: number; + options?: number[]; + value?: number; onChange(value: number): void; } -export const ResultsPerPageView: React.FC<Props> = ({ onChange, options, value }) => { +export const ResultsPerPageView: React.FC<Props> = ({ onChange, options = [], value = 20 }) => { // If we don't have the value in options, unset it const selectedValue = value && !options.includes(value) ? undefined : value; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index fd2ed0693257d..5b4d1fb23ef0b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -8,6 +8,8 @@ import { kea, MakeLogicType } from 'kea'; import { omit, cloneDeep, isEmpty } from 'lodash'; +import type { SearchResult } from '@elastic/search-ui'; + import { flashSuccessToast, flashAPIErrors, @@ -17,7 +19,6 @@ import { HttpLogic } from '../../../shared/http'; import { Schema, SchemaConflicts } from '../../../shared/schema/types'; import { EngineLogic } from '../engine'; -import { Result } from '../result/types'; import { UPDATE_SUCCESS_MESSAGE, @@ -47,7 +48,7 @@ interface RelevanceTuningActions { setSearchSettings(searchSettings: SearchSettings): { searchSettings: SearchSettings }; setFilterValue(value: string): string; setSearchQuery(value: string): string; - setSearchResults(searchResults: Result[]): Result[]; + setSearchResults(searchResults: SearchResult[]): SearchResult[]; setResultsLoading(resultsLoading: boolean): boolean; clearSearchResults(): void; resetSearchSettingsState(): void; @@ -104,7 +105,7 @@ interface RelevanceTuningValues { query: string; unsavedChanges: boolean; dataLoading: boolean; - searchResults: Result[] | null; + searchResults: SearchResult[] | null; resultsLoading: boolean; } @@ -278,7 +279,7 @@ export const RelevanceTuningLogic = kea< const filteredBoosts = removeEmptyValueBoosts(boosts); try { - const response = await http.post<{ results: Result[] }>(url, { + const response = await http.post<{ results: SearchResult[] }>(url, { query: { query, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 9d5d49b58bf5d..0c78cc672a609 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -12,6 +12,7 @@ import './result.scss'; import { EuiPanel, EuiIcon } from '@elastic/eui'; +import type { SearchResult } from '@elastic/search-ui'; import { i18n } from '@kbn/i18n'; import { KibanaLogic } from '../../../shared/kibana'; @@ -23,10 +24,10 @@ import { flattenDocument } from '../../utils/results'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; -import { FieldValue, Result as ResultType, ResultAction } from './types'; +import { FieldValue, ResultAction } from './types'; interface Props { - result: ResultType; + result: SearchResult; isMetaEngine: boolean; showScore?: boolean; resultPosition?: number; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts index f8f579fa32f80..9ffc535edaca2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/types.ts @@ -25,18 +25,6 @@ export interface ResultMeta { clicks?: number; } -// A search result item -export type Result = { - id: { - raw: string; - }; - _meta: ResultMeta; -} & { - // this should be a FieldType object, but there's no good way to do that in TS: https://github.com/microsoft/TypeScript/issues/17867 - // You'll need to cast it to FieldValue whenever you use it. - [key: string]: object; -}; - export interface ResultAction { onClick(): void; title: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts index e21b529474bf7..4097e693aff11 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.ts @@ -7,23 +7,23 @@ import { kea, MakeLogicType } from 'kea'; +import type { SearchResult } from '@elastic/search-ui'; + import { flashAPIErrors } from '../../../shared/flash_messages'; import { HttpLogic } from '../../../shared/http'; import { flattenDocument } from '../../utils/results'; import { EngineLogic } from '../engine'; -import { Result } from '../result/types'; - interface SearchValues { searchDataLoading: boolean; searchQuery: string; - searchResults: Result[]; + searchResults: SearchResult[]; } interface SearchActions { search(query: string): { query: string }; - onSearch({ results }: { results: Result[] }): { results: Result[] }; + onSearch({ results }: { results: SearchResult[] }): { results: SearchResult[] }; } export const SearchLogic = kea<MakeLogicType<SearchValues, SearchActions>>({ @@ -50,7 +50,7 @@ export const SearchLogic = kea<MakeLogicType<SearchValues, SearchActions>>({ searchResults: [ [], { - onSearch: (_, { results }) => results.map((res) => flattenDocument(res) as Result), + onSearch: (_, { results }) => results.map((res) => flattenDocument(res) as SearchResult), }, ], }), @@ -62,7 +62,7 @@ export const SearchLogic = kea<MakeLogicType<SearchValues, SearchActions>>({ const { engineName } = EngineLogic.values; try { - const response = await http.post<{ results: Result[] }>( + const response = await http.post<{ results: SearchResult[] }>( `/internal/app_search/engines/${engineName}/search`, { query: { query } } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts index 126bb5ade054d..c330ce62b474a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/results/index.test.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Result } from '../../components/result/types'; +import type { SearchResult } from '@elastic/search-ui'; import { flattenDocument, flattenDocumentField } from '.'; @@ -42,13 +42,13 @@ describe('flattenDocumentField', () => { describe('flattenDocument', () => { it('flattens all fields without raw key', () => { - const result: Result = { + const result: SearchResult = { id: { raw: '123' }, _meta: { engine: 'Test', id: '1' }, title: { raw: 'Getty Museum' }, address: { city: { raw: 'Los Angeles' }, state: { raw: 'California' } }, }; - const expected: Result = { + const expected: SearchResult = { id: { raw: '123' }, _meta: { engine: 'Test', id: '1' }, title: { raw: 'Getty Museum' }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_settings/crawler_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_settings/crawler_settings.tsx deleted file mode 100644 index 57ace39031cd1..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_settings/crawler_settings.tsx +++ /dev/null @@ -1,22 +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 from 'react'; - -import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; - -export const CrawlerSettings: React.FC = () => { - return ( - <EnterpriseSearchContentPageTemplate - pageChrome={[]} - pageViewTelemetry="Connector Settings" - isLoading={false} - > - <>Crawler Settings</> - </EnterpriseSearchContentPageTemplate> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.test.tsx index a7757c6410c82..9b3375f1d168f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.test.tsx @@ -37,16 +37,10 @@ describe('useEnterpriseSearchContentNav', () => { name: 'Search indices', }, { - href: '/connector_settings', - id: 'connector_settings', + href: '/settings', + id: 'settings', items: undefined, - name: 'Connector settings', - }, - { - href: '/crawler_settings', - id: 'crawler_settings', - items: undefined, - name: 'Web crawler settings', + name: 'Settings', }, ], }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.tsx index 7b6baea27eff7..af3f5dd9c48e8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/layout/nav.tsx @@ -15,12 +15,7 @@ import { } from '../../../../../common/constants'; import { generateNavLink } from '../../../shared/layout'; -import { - ROOT_PATH, - SEARCH_INDICES_PATH, - CONNECTOR_SETTINGS_PATH, - CRAWLER_SETTINGS_PATH, -} from '../../routes'; +import { ROOT_PATH, SEARCH_INDICES_PATH, SETTINGS_PATH } from '../../routes'; import { useSearchIndicesNav } from '../search_index/index_nav'; @@ -61,22 +56,12 @@ export const useEnterpriseSearchContentNav = () => { }), }, { - id: 'connector_settings', - name: i18n.translate('xpack.enterpriseSearch.content.nav.connectorSettingsTitle', { - defaultMessage: 'Connector settings', - }), - ...generateNavLink({ - to: CONNECTOR_SETTINGS_PATH, - isRoot: true, - }), - }, - { - id: 'crawler_settings', - name: i18n.translate('xpack.enterpriseSearch.content.nav.crawlerSettingsTitle', { - defaultMessage: 'Web crawler settings', + id: 'settings', + name: i18n.translate('xpack.enterpriseSearch.content.nav.settingsTitle', { + defaultMessage: 'Settings', }), ...generateNavLink({ - to: CRAWLER_SETTINGS_PATH, + to: SETTINGS_PATH, isRoot: true, }), }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx index a61f1a26739a7..b4587025a551a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_indices/search_indices.tsx @@ -263,10 +263,12 @@ export const SearchIndices: React.FC = () => { /> </> ) : ( - <AddContentEmptyPrompt /> + <> + <AddContentEmptyPrompt /> + <EuiSpacer size="xxl" /> + {engineSteps} + </> )} - <EuiSpacer size="xxl" /> - {indices.length === 0 && !isLoading && engineSteps} </EnterpriseSearchContentPageTemplate> ) </> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/index.ts similarity index 82% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/index.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/index.ts index 83d1f526c36cb..5f8bbf1491904 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { OverviewContent } from './overview_content'; +export { Settings } from './settings'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_settings/connector_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx similarity index 53% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_settings/connector_settings.tsx rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx index 3ab770100894c..4255d467d4676 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connector_settings/connector_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/settings/settings.tsx @@ -7,16 +7,25 @@ import React from 'react'; +import { i18n } from '@kbn/i18n'; + import { EnterpriseSearchContentPageTemplate } from '../layout/page_template'; -export const ConnectorSettings: React.FC = () => { +export const Settings: React.FC = () => { return ( <EnterpriseSearchContentPageTemplate - pageChrome={[]} - pageViewTelemetry="Connector Settings" + pageChrome={[ + i18n.translate('xpack.enterpriseSearch.content.searchIndices.content.breadcrumb', { + defaultMessage: 'Content', + }), + i18n.translate('xpack.enterpriseSearch.content.settings.breadcrumb', { + defaultMessage: 'Settings', + }), + ]} + pageViewTelemetry="Settings" isLoading={false} > - <>Connector Settings</> + <>Settings</> </EnterpriseSearchContentPageTemplate> ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.test.tsx index 4d96af036d52f..fb0752fea20f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.test.tsx @@ -19,10 +19,9 @@ import { shallow } from 'enzyme'; import { SetupGuide } from '../enterprise_search_overview/components/setup_guide'; import { VersionMismatchPage } from '../shared/version_mismatch'; -import { ConnectorSettings } from './components/connector_settings'; -import { CrawlerSettings } from './components/crawler_settings'; import { ErrorConnecting } from './components/error_connecting'; import { SearchIndicesRouter } from './components/search_indices'; +import { Settings } from './components/settings'; import { EnterpriseSearchContent, @@ -81,7 +80,6 @@ describe('EnterpriseSearchContentConfigured', () => { it('renders engine routes', () => { expect(wrapper.find(SearchIndicesRouter)).toHaveLength(1); - expect(wrapper.find(ConnectorSettings)).toHaveLength(1); - expect(wrapper.find(CrawlerSettings)).toHaveLength(1); + expect(wrapper.find(Settings)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx index c4f64bacb9ab8..4f73c141d17c6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/index.tsx @@ -17,18 +17,11 @@ import { HttpLogic } from '../shared/http'; import { KibanaLogic } from '../shared/kibana'; import { VersionMismatchPage } from '../shared/version_mismatch'; -import { ConnectorSettings } from './components/connector_settings'; -import { CrawlerSettings } from './components/crawler_settings'; import { ErrorConnecting } from './components/error_connecting'; import { NotFound } from './components/not_found'; import { SearchIndicesRouter } from './components/search_indices'; -import { - SETUP_GUIDE_PATH, - ROOT_PATH, - SEARCH_INDICES_PATH, - CONNECTOR_SETTINGS_PATH, - CRAWLER_SETTINGS_PATH, -} from './routes'; +import { Settings } from './components/settings'; +import { SETUP_GUIDE_PATH, ROOT_PATH, SEARCH_INDICES_PATH, SETTINGS_PATH } from './routes'; export const EnterpriseSearchContent: React.FC<InitialAppData> = (props) => { const { config } = useValues(KibanaLogic); @@ -78,11 +71,8 @@ export const EnterpriseSearchContentConfigured: React.FC<Required<InitialAppData <Route path={SEARCH_INDICES_PATH}> <SearchIndicesRouter /> </Route> - <Route path={CONNECTOR_SETTINGS_PATH}> - <ConnectorSettings /> - </Route> - <Route path={CRAWLER_SETTINGS_PATH}> - <CrawlerSettings /> + <Route path={SETTINGS_PATH}> + <Settings /> </Route> <Route> <NotFound /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts index 821a90e07adb4..b429b4b531997 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/routes.ts @@ -10,8 +10,7 @@ export const ROOT_PATH = '/'; export const SETUP_GUIDE_PATH = '/setup_guide'; export const SEARCH_INDICES_PATH = `${ROOT_PATH}search_indices`; -export const CONNECTOR_SETTINGS_PATH = `${ROOT_PATH}connector_settings`; -export const CRAWLER_SETTINGS_PATH = `${ROOT_PATH}crawler_settings`; +export const SETTINGS_PATH = `${ROOT_PATH}settings`; export const NEW_INDEX_PATH = `${SEARCH_INDICES_PATH}/new_index`; export const NEW_API_PATH = `${NEW_INDEX_PATH}/api`; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/overview_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/overview_content.test.tsx deleted file mode 100644 index 53c31eaf0498a..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/overview_content.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { setMockValues } from '../../../__mocks__/kea_logic'; - -import React from 'react'; - -import { shallow } from 'enzyme'; - -import { EuiEmptyPrompt } from '@elastic/eui'; - -import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt'; -import { GettingStartedSteps } from '../../../shared/getting_started_steps'; - -import { LicenseCallout } from '../license_callout'; -import { SetupGuideCta } from '../setup_guide'; -import { TrialCallout } from '../trial_callout'; - -import { OverviewContent } from '.'; - -describe('OverviewContent', () => { - const props = { - access: {}, - isWorkplaceSearchAdmin: true, - }; - - it('renders the overview page, Getting Started steps & setup guide CTAs with no host set', () => { - setMockValues({ config: { host: '' } }); - const wrapper = shallow(<OverviewContent {...props} />); - - expect(wrapper.find(GettingStartedSteps)).toHaveLength(1); - expect(wrapper.find(AddContentEmptyPrompt)).toHaveLength(1); - expect(wrapper.find(SetupGuideCta)).toHaveLength(1); - expect(wrapper.find(LicenseCallout)).toHaveLength(0); - }); - - it('renders the trial callout', () => { - setMockValues({ config: { host: 'localhost' } }); - const wrapper = shallow(<OverviewContent {...props} />); - - expect(wrapper.find(TrialCallout)).toHaveLength(1); - }); - - // TODO Refactor this and other tests according to the search indices permissions - describe('access checks when host is set', () => { - beforeEach(() => { - setMockValues({ config: { host: 'localhost' } }); - }); - - it('renders the license callout when user has access to a product', () => { - setMockValues({ config: { host: 'localhost' } }); - const wrapper = shallow( - <OverviewContent {...props} access={{ hasWorkplaceSearchAccess: true }} /> - ); - - expect(wrapper.find(LicenseCallout)).toHaveLength(1); - }); - - it('renders empty prompt and overview or license callout if the user does not have access', () => { - const wrapper = shallow(<OverviewContent {...props} />); - - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - expect(wrapper.find(GettingStartedSteps)).toHaveLength(0); - expect(wrapper.find(AddContentEmptyPrompt)).toHaveLength(0); - expect(wrapper.find(LicenseCallout)).toHaveLength(0); - expect(wrapper.find(SetupGuideCta)).toHaveLength(0); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/overview_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/overview_content.tsx deleted file mode 100644 index 3824a09a0bc1c..0000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/overview_content/overview_content.tsx +++ /dev/null @@ -1,159 +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 from 'react'; - -import { useValues } from 'kea'; - -import { - EuiButton, - EuiEmptyPrompt, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiLink, - EuiPageBody, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { Chat } from '@kbn/cloud-plugin/public'; -import { i18n } from '@kbn/i18n'; - -import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt'; -import { docLinks } from '../../../shared/doc_links'; -import { ElasticsearchResources } from '../../../shared/elasticsearch_resources'; -import { GettingStartedSteps } from '../../../shared/getting_started_steps'; -import { KibanaLogic } from '../../../shared/kibana'; -import { SetEnterpriseSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; -import { SendEnterpriseSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; - -import { EnterpriseSearchOverviewPageTemplate } from '../layout'; -import { LicenseCallout } from '../license_callout'; -import illustration from '../product_selector/lock_light.svg'; -import { SetupGuideCta } from '../setup_guide'; - -import { TrialCallout } from '../trial_callout'; - -interface OverviewContentProps { - access: { - hasAppSearchAccess?: boolean; - hasWorkplaceSearchAccess?: boolean; - }; - isWorkplaceSearchAdmin: boolean; -} - -export const OverviewContent: React.FC<OverviewContentProps> = ({ access }) => { - const { hasAppSearchAccess, hasWorkplaceSearchAccess } = access; - const { config } = useValues(KibanaLogic); - - // TODO Refactor this and the tests according to the search indices permissions - // If Enterprise Search has been set up and the user does not have access to either product, show a message saying they - // need to contact an administrator to get access to one of the products. - const shouldShowEnterpriseSearchOverview = - !config.host || hasAppSearchAccess || hasWorkplaceSearchAccess; - - const enterpriseSearchOverview = ( - <> - <AddContentEmptyPrompt /> - <EuiSpacer size="xxl" /> - <EuiFlexGroup> - <EuiFlexItem> - <GettingStartedSteps /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <ElasticsearchResources /> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="xxl" /> - - {config.host ? <LicenseCallout /> : <SetupGuideCta />} - </> - ); - - const insufficientAccessMessage = ( - <EuiEmptyPrompt - icon={ - <EuiImage - size="fullWidth" - src={illustration} - alt={i18n.translate( - 'xpack.enterpriseSearch.overviewContent.insufficientPermissionsIllustration', - { defaultMessage: 'Insufficient permissions illustration' } - )} - /> - } - title={ - <h2> - {i18n.translate('xpack.enterpriseSearch.overviewContent.insufficientPermissionsTitle', { - defaultMessage: 'Insufficient permissions', - })} - </h2> - } - layout="horizontal" - color="plain" - body={ - <> - <p> - {i18n.translate('xpack.enterpriseSearch.overviewContent.insufficientPermissionsBody', { - defaultMessage: - 'You don’t have access to view this page. If you feel this may be an error, please contact your administrator.', - })} - </p> - </> - } - actions={ - <EuiButton color="primary" fill href="/"> - {i18n.translate( - 'xpack.enterpriseSearch.overviewContent.insufficientPermissionsButtonLabel', - { - defaultMessage: 'Go to the Kibana dashboard', - } - )} - </EuiButton> - } - footer={ - <> - <EuiTitle size="xxs"> - <span> - {i18n.translate( - 'xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterBody', - { - defaultMessage: 'Go to the Kibana dashboard', - } - )} - </span> - </EuiTitle>{' '} - <EuiLink href={docLinks.kibanaSecurity} target="_blank"> - {i18n.translate( - 'xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterLinkLabel', - { - defaultMessage: 'Read documentation', - } - )} - </EuiLink> - </> - } - /> - ); - return ( - <EnterpriseSearchOverviewPageTemplate - pageHeader={{ - pageTitle: i18n.translate('xpack.enterpriseSearch.overviewContent.heading', { - defaultMessage: 'Welcome to Enterprise Search', - }), - }} - > - <SetPageChrome /> - <SendTelemetry action="viewed" metric="overview" /> - <TrialCallout /> - <EuiPageBody paddingSize="none"> - {shouldShowEnterpriseSearchOverview ? enterpriseSearchOverview : insufficientAccessMessage} - <Chat /> - </EuiPageBody> - </EnterpriseSearchOverviewPageTemplate> - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/getting_started_steps/getting_started_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/getting_started_steps/getting_started_steps.tsx index 7081e2146b61e..bf389f8b81581 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/getting_started_steps/getting_started_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/getting_started_steps/getting_started_steps.tsx @@ -5,20 +5,9 @@ * 2.0. */ -import React, { useState } from 'react'; +import React from 'react'; -import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiPopover, - EuiSpacer, - EuiSteps, - EuiText, - EuiContextMenuPanel, - EuiContextMenuItem, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiSteps, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ELASTICSEARCH_PLUGIN } from '../../../../common/constants'; @@ -32,9 +21,6 @@ export interface GettingStartedStepsProps { } export const GettingStartedSteps: React.FC<GettingStartedStepsProps> = ({ step = 'first' }) => { - // TODO replace with logic file - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - return ( <EuiFlexGroup> <EuiFlexItem> @@ -84,65 +70,6 @@ export const GettingStartedSteps: React.FC<GettingStartedStepsProps> = ({ step = </EuiText> <EuiSpacer size="m" /> <EuiFlexGroup alignItems="center" gutterSize="s"> - <EuiFlexItem grow={false}> - <EuiPopover - button={ - <EuiButton - disabled={step !== 'second'} - iconType="arrowDown" - iconSide="right" - fill - onClick={() => setIsPopoverOpen(!isPopoverOpen)} - > - {i18n.translate( - 'xpack.enterpriseSearch.overview.gettingStartedSteps.createASearchEngineButton', - { defaultMessage: 'Create a search engine' } - )} - </EuiButton> - } - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - > - {/* TODO add onclick for these links*/} - <EuiContextMenuPanel - items={[ - <EuiContextMenuItem key="" onClick={() => {}}> - <EuiText size="s"> - <h4> - {i18n.translate( - 'xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.title', - { defaultMessage: 'Create an App Search engine' } - )} - </h4> - {i18n.translate( - 'xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.description', - { - defaultMessage: - 'All the power of Elasticsearch, without the learning curve.', - } - )} - </EuiText> - </EuiContextMenuItem>, - <EuiContextMenuItem key="" onClick={() => {}}> - <EuiText size="s"> - <h4> - {i18n.translate( - 'xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.title', - { defaultMessage: 'Create a Workplace Search group' } - )} - </h4> - {i18n.translate( - 'xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.description', - { - defaultMessage: 'A secure search experience for internal teams', - } - )} - </EuiText> - </EuiContextMenuItem>, - ]} - /> - </EuiPopover> - </EuiFlexItem> <EuiFlexItem grow={false}> <EuiLinkTo shouldNotCreateHref to={ELASTICSEARCH_PLUGIN.URL}> <EuiIcon type="iInCircle" /> diff --git a/x-pack/plugins/fleet/.storybook/context/fixtures/packages.ts b/x-pack/plugins/fleet/.storybook/context/fixtures/packages.ts index a3e436b3f9718..cb902de21a278 100644 --- a/x-pack/plugins/fleet/.storybook/context/fixtures/packages.ts +++ b/x-pack/plugins/fleet/.storybook/context/fixtures/packages.ts @@ -63,6 +63,7 @@ export const items: GetPackagesResponse['items'] = [ install_started_at: '2021-08-25T19:44:37.078Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], coreMigrationVersion: '7.14.0', @@ -100,6 +101,7 @@ export const items: GetPackagesResponse['items'] = [ install_started_at: '2021-08-25T19:44:37.078Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], coreMigrationVersion: '7.14.0', @@ -162,6 +164,7 @@ export const items: GetPackagesResponse['items'] = [ install_started_at: '2021-08-25T19:44:37.078Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], coreMigrationVersion: '7.14.0', @@ -199,6 +202,7 @@ export const items: GetPackagesResponse['items'] = [ install_started_at: '2021-08-25T19:44:37.078Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], coreMigrationVersion: '7.14.0', @@ -270,6 +274,7 @@ export const items: GetPackagesResponse['items'] = [ install_started_at: '2021-08-25T19:44:37.078Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], coreMigrationVersion: '7.14.0', @@ -307,6 +312,7 @@ export const items: GetPackagesResponse['items'] = [ install_started_at: '2021-08-25T19:44:37.078Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], coreMigrationVersion: '7.14.0', diff --git a/x-pack/plugins/fleet/common/experimental_features.ts b/x-pack/plugins/fleet/common/experimental_features.ts index 9fd95d6843e12..9a49e403671e2 100644 --- a/x-pack/plugins/fleet/common/experimental_features.ts +++ b/x-pack/plugins/fleet/common/experimental_features.ts @@ -13,6 +13,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues; */ export const allowedExperimentalValues = Object.freeze({ createPackagePolicyMultiPageLayout: true, + packageVerification: false, }); type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index d33616e369abe..890149640be78 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -134,6 +134,7 @@ type RegistryOverridesToOptional = Pick<PackageSpecManifest, 'title' | 'release' interface RegistryAdditionalProperties { assets?: string[]; download: string; + signature_path?: string; path: string; readme?: string; internal?: boolean; // Registry addition[0] and EPM uses it[1] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L63 [1] @@ -400,6 +401,7 @@ export interface IntegrationCardItem { fromIntegrations?: string; } +export type PackageVerificationStatus = 'verified' | 'unverified' | 'unknown'; export type PackagesGroupedByStatus = Record<ValueOf<InstallationStatus>, PackageList>; export type PackageInfo = | Installable<Merge<RegistryPackage, EpmPackageAdditions>> @@ -419,6 +421,8 @@ export interface Installation extends SavedObjectAttributes { installed_kibana_space_id?: string; keep_policies_up_to_date?: boolean; install_format_schema_version?: string; + verification_status: PackageVerificationStatus; + verification_key_id?: string | null; } export interface PackageUsageStats { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts index 4da59560e408e..7721025a75067 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/hooks/use_wait_for_fleet_server.ts @@ -6,9 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; -import { sendGetFleetStatus, useStartServices } from '../../../hooks'; +import { useFleetStatus, useStartServices } from '../../../hooks'; const REFRESH_INTERVAL = 10000; @@ -17,26 +17,25 @@ const REFRESH_INTERVAL = 10000; * in the `missing_requirements` list. */ export const useWaitForFleetServer = () => { - const [isFleetServerReady, setIsFleetServerReady] = useState(false); + const fleetStatus = useFleetStatus(); const { notifications } = useStartServices(); + const isFleetServerReady = + fleetStatus.isReady && !fleetStatus.missingRequirements?.includes('fleet_server'); + useEffect(() => { let interval: ReturnType<typeof setInterval> | null = null; if (!isFleetServerReady) { interval = setInterval(async () => { try { - const res = await sendGetFleetStatus(); - - if (res.error) { - throw res.error; - } - if (res.data?.isReady && !res.data?.missing_requirements?.includes('fleet_server')) { - setIsFleetServerReady(true); - + if (isFleetServerReady) { if (interval) { clearInterval(interval); } + } else { + fleetStatus.setForceDisplayInstructions(true); + fleetStatus.refresh(); } } catch (err) { notifications.toasts.addError(err, { @@ -55,7 +54,7 @@ export const useWaitForFleetServer = () => { }; return cleanup; - }, [notifications.toasts, isFleetServerReady]); + }, [notifications.toasts, isFleetServerReady, fleetStatus]); return { isFleetServerReady }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx index 15e8609191019..8111fd3f09ac6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/fleet_server_instructions/steps/confirm_fleet_server_connection.tsx @@ -13,7 +13,7 @@ import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useFlyoutContext } from '../../../hooks'; +import { useFleetStatus, useFlyoutContext } from '../../../hooks'; export function getConfirmFleetServerConnectionStep({ disabled, @@ -41,6 +41,12 @@ const ConfirmFleetServerConnectionStepContent: React.FunctionComponent<{ isFleetServerReady: boolean; }> = ({ isFleetServerReady }) => { const flyoutContext = useFlyoutContext(); + const fleetStatus = useFleetStatus(); + + const handleContinueClick = () => { + fleetStatus.forceDisplayInstructions = false; + flyoutContext.openEnrollmentFlyout(); + }; return isFleetServerReady ? ( <> @@ -53,7 +59,7 @@ const ConfirmFleetServerConnectionStepContent: React.FunctionComponent<{ <EuiSpacer size="m" /> - <EuiButton color="primary" onClick={flyoutContext.openEnrollmentFlyout}> + <EuiButton color="primary" onClick={handleContinueClick}> <FormattedMessage id="xpack.fleet.fleetServerFlyout.continueEnrollingButton" defaultMessage="Continue enrolling Elastic Agent" diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx index 9cffb4d196bbe..b87b09c7f58c4 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_overview.tsx @@ -25,6 +25,7 @@ import { useKibanaVersion } from '../../../../../hooks'; import { isAgentUpgradeable } from '../../../../../services'; import { AgentPolicySummaryLine } from '../../../../../components'; import { AgentHealth } from '../../../components'; +import { Tags } from '../../../agent_list_page/components/tags'; // Allows child text to be truncated const FlexItemWithMinWidth = styled(EuiFlexItem)` @@ -174,6 +175,12 @@ export const AgentDetailsOverviewSection: React.FunctionComponent<{ /> ), }, + { + title: i18n.translate('xpack.fleet.agentDetails.tagsLabel', { + defaultMessage: 'Tags', + }), + description: (agent.tags ?? []).length > 0 ? <Tags tags={agent.tags ?? []} /> : '-', + }, ].map(({ title, description }) => { return ( <EuiFlexGroup> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx index 18f7ed8c27905..696ccfb4faff7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/search_and_filter_bar.tsx @@ -89,6 +89,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{ selectedAgents: Agent[]; refreshAgents: (args?: { refreshTags?: boolean }) => void; onClickAddAgent: () => void; + onClickAddFleetServer: () => void; visibleAgents: Agent[]; }> = ({ agentPolicies, @@ -111,6 +112,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{ selectedAgents, refreshAgents, onClickAddAgent, + onClickAddFleetServer, visibleAgents, }) => { // Policies state for filtering @@ -321,16 +323,47 @@ export const SearchAndFilterBar: React.FunctionComponent<{ </EuiFilterButton> </EuiFilterGroup> </EuiFlexItem> - <EuiFlexItem> - <EuiButton - fill - iconType="plusInCircle" - onClick={() => onClickAddAgent()} - data-test-subj="addAgentButton" - > - <FormattedMessage id="xpack.fleet.agentList.addButton" defaultMessage="Add agent" /> - </EuiButton> - </EuiFlexItem> + {selectedAgents.length === 0 && ( + <> + <EuiFlexItem> + <EuiToolTip + content={ + <FormattedMessage + id="xpack.fleet.agentList.addFleetServerButton.tooltip" + defaultMessage="Fleet Server is a component of the Elastic Stack used to centrally manage Elastic Agents" + /> + } + > + <EuiButton + onClick={onClickAddFleetServer} + data-test-subj="addFleetServerButton" + > + <FormattedMessage + id="xpack.fleet.agentList.addFleetServerButton" + defaultMessage="Add Fleet Server" + /> + </EuiButton> + </EuiToolTip> + </EuiFlexItem> + <EuiFlexItem> + <EuiToolTip + content={ + <FormattedMessage + id="xpack.fleet.agentList.addAgentButton.tooltip" + defaultMessage="Add Elastic Agents to your hosts to collect data and send it to the Elastic Stack" + /> + } + > + <EuiButton fill onClick={onClickAddAgent} data-test-subj="addAgentButton"> + <FormattedMessage + id="xpack.fleet.agentList.addButton" + defaultMessage="Add agent" + /> + </EuiButton> + </EuiToolTip> + </EuiFlexItem> + </> + )} <EuiFlexItem grow={false}> <AgentBulkActions totalAgents={totalAgents} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx new file mode 100644 index 0000000000000..f4b72f9e9b53b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.test.tsx @@ -0,0 +1,89 @@ +/* + * 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 { debounce } from 'lodash'; +import { render, fireEvent, waitFor } from '@testing-library/react'; + +import { useUpdateTags } from '../hooks'; + +import { TagOptions } from './tag_options'; + +jest.mock('lodash', () => ({ + debounce: jest.fn(), +})); + +jest.mock('../hooks', () => ({ + useUpdateTags: jest.fn().mockReturnValue({ + bulkUpdateTags: jest.fn(), + }), +})); + +describe('TagOptions', () => { + const mockBulkUpdateTags = useUpdateTags().bulkUpdateTags as jest.Mock; + const onTagsUpdated = jest.fn(); + let isTagHovered: boolean; + + beforeEach(() => { + onTagsUpdated.mockReset(); + mockBulkUpdateTags.mockReset(); + mockBulkUpdateTags.mockResolvedValue({}); + isTagHovered = true; + (debounce as jest.Mock).mockImplementationOnce((fn) => (newName: string) => { + fn(newName); + onTagsUpdated(); + }); + }); + + const renderComponent = () => { + return render( + <div> + <TagOptions tagName={'agent'} isTagHovered={isTagHovered} onTagsUpdated={onTagsUpdated} /> + </div> + ); + }; + + it('should make menu button visible when tag is hovered', async () => { + isTagHovered = false; + const result = renderComponent(); + expect(result.container.querySelector('[aria-label="Tag Options"]')).toBeNull(); + + isTagHovered = true; + await waitFor(() => { + expect(result.container.querySelector('[aria-label="Tag Options"]')).toBeDefined(); + }); + }); + + it('should delete tag when button is clicked', () => { + const result = renderComponent(); + + fireEvent.click(result.getByRole('button')); + + fireEvent.click(result.getByText('Delete tag')); + + expect(mockBulkUpdateTags).toHaveBeenCalledWith('tags:agent', [], ['agent'], expect.anything()); + }); + + it('should rename tag when name input is changed', async () => { + const result = renderComponent(); + + fireEvent.click(result.getByRole('button')); + + const nameInput = result.getByDisplayValue('agent'); + fireEvent.input(nameInput, { + target: { value: 'newName' }, + }); + + expect(onTagsUpdated).toHaveBeenCalled(); + expect(mockBulkUpdateTags).toHaveBeenCalledWith( + 'tags:agent', + ['newName'], + ['agent'], + expect.anything() + ); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx new file mode 100644 index 0000000000000..62d25d83938b1 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tag_options.tsx @@ -0,0 +1,119 @@ +/* + * 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, { useMemo, useState, useEffect } from 'react'; +import { debounce } from 'lodash'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiWrappingPopover, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; + +import { useUpdateTags } from '../hooks'; + +interface Props { + tagName: string; + isTagHovered: boolean; + onTagsUpdated: () => void; +} + +export const TagOptions: React.FC<Props> = ({ tagName, isTagHovered, onTagsUpdated }: Props) => { + const [tagOptionsVisible, setTagOptionsVisible] = useState<boolean>(false); + const [tagOptionsButton, setTagOptionsButton] = useState<HTMLElement>(); + + const [tagMenuButtonVisible, setTagMenuButtonVisible] = useState<boolean>(isTagHovered); + + useEffect(() => { + setTagMenuButtonVisible(isTagHovered || tagOptionsVisible); + }, [isTagHovered, tagOptionsVisible]); + + const [updatedName, setUpdatedName] = useState<string | undefined>(tagName); + + const closePopover = () => setTagOptionsVisible(false); + + const updateTagsHook = useUpdateTags(); + + const TAGS_QUERY = 'tags:{name}'; + + const debouncedSendRenameTag = useMemo( + () => + debounce((newName: string) => { + const kuery = TAGS_QUERY.replace('{name}', tagName); + updateTagsHook.bulkUpdateTags(kuery, [newName], [tagName], () => onTagsUpdated()); + }, 1000), + [onTagsUpdated, tagName, updateTagsHook] + ); + + return ( + <> + {tagMenuButtonVisible && ( + <EuiButtonIcon + iconType="boxesHorizontal" + aria-label={i18n.translate('xpack.fleet.tagOptions.tagOptionsToggleButtonLabel', { + defaultMessage: 'Tag Options', + })} + color="text" + onClick={(event: any) => { + setTagOptionsButton(event.target); + setTagOptionsVisible(!tagOptionsVisible); + }} + /> + )} + {tagOptionsVisible && ( + <EuiWrappingPopover + isOpen={true} + button={tagOptionsButton!} + closePopover={closePopover} + anchorPosition="downCenter" + > + <EuiFlexGroup direction="column" alignItems="flexStart" gutterSize="xs"> + <EuiFlexItem> + <EuiFieldText + placeholder={i18n.translate('xpack.fleet.tagOptions.nameTextFieldPlaceholder', { + defaultMessage: 'Enter new name for tag', + })} + value={updatedName} + required + onChange={(e: any) => { + const newName = e.target.value; + setUpdatedName(newName); + if (!newName) { + return; + } + debouncedSendRenameTag(newName); + }} + /> + </EuiFlexItem> + <EuiFlexItem> + <EuiButtonEmpty + size="s" + color="danger" + onClick={() => { + const kuery = TAGS_QUERY.replace('{name}', tagName); + updateTagsHook.bulkUpdateTags(kuery, [], [tagName], () => onTagsUpdated()); + closePopover(); + }} + > + <EuiIcon type="trash" />{' '} + <FormattedMessage + id="xpack.fleet.tagOptions.deleteText" + defaultMessage="Delete tag" + /> + </EuiButtonEmpty> + </EuiFlexItem> + </EuiFlexGroup> + </EuiWrappingPopover> + )} + </> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx index 03ba43d34ce43..4d4da36b9cb5f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.test.tsx @@ -30,6 +30,7 @@ describe('TagsAddRemove', () => { beforeEach(() => { onTagsUpdated.mockReset(); mockUpdateTags.mockReset(); + mockBulkUpdateTags.mockReset(); allTags = ['tag1', 'tag2']; selectedTags = ['tag1']; }); @@ -47,23 +48,27 @@ describe('TagsAddRemove', () => { ); }; - it('should add selected tag when previously unselected', () => { + it('should add selected tag when previously unselected', async () => { + mockUpdateTags.mockImplementation(() => { + selectedTags = ['tag1', 'tag2']; + }); const result = renderComponent('agent1'); - const getTag = (name: string) => result.getByText(name).closest('li')!; + const getTag = (name: string) => result.getByText(name); fireEvent.click(getTag('tag2')); - expect(getTag('tag2').getAttribute('aria-checked')).toEqual('true'); expect(mockUpdateTags).toHaveBeenCalledWith('agent1', ['tag1', 'tag2'], expect.anything()); }); - it('should remove selected tag when previously selected', () => { + it('should remove selected tag when previously selected', async () => { + mockUpdateTags.mockImplementation(() => { + selectedTags = []; + }); const result = renderComponent('agent1'); - const getTag = (name: string) => result.getByText(name).closest('li')!; + const getTag = (name: string) => result.getByText(name); fireEvent.click(getTag('tag1')); - expect(getTag('tag1').getAttribute('aria-checked')).toEqual('false'); expect(mockUpdateTags).toHaveBeenCalledWith('agent1', [], expect.anything()); }); @@ -80,23 +85,27 @@ describe('TagsAddRemove', () => { expect(mockUpdateTags).toHaveBeenCalledWith('agent1', ['tag1', 'newTag'], expect.anything()); }); - it('should add selected tag when previously unselected - bulk selection', () => { + it('should add selected tag when previously unselected - bulk selection', async () => { + mockBulkUpdateTags.mockImplementation(() => { + selectedTags = ['tag1', 'tag2']; + }); const result = renderComponent(undefined, ''); - const getTag = (name: string) => result.getByText(name).closest('li')!; + const getTag = (name: string) => result.getByText(name); fireEvent.click(getTag('tag2')); - expect(getTag('tag2').getAttribute('aria-checked')).toEqual('true'); expect(mockBulkUpdateTags).toHaveBeenCalledWith('', ['tag2'], [], expect.anything()); }); - it('should remove selected tag when previously selected - bulk selection', () => { + it('should remove selected tag when previously selected - bulk selection', async () => { + mockBulkUpdateTags.mockImplementation(() => { + selectedTags = []; + }); const result = renderComponent(undefined, ['agent1', 'agent2']); - const getTag = (name: string) => result.getByText(name).closest('li')!; + const getTag = (name: string) => result.getByText(name); fireEvent.click(getTag('tag1')); - expect(getTag('tag1').getAttribute('aria-checked')).toEqual('false'); expect(mockBulkUpdateTags).toHaveBeenCalledWith( ['agent1', 'agent2'], [], @@ -117,4 +126,16 @@ describe('TagsAddRemove', () => { expect(mockBulkUpdateTags).toHaveBeenCalledWith('query', ['newTag'], [], expect.anything()); }); + + it('should make tag options button visible on mouse enter', async () => { + const result = renderComponent('agent1'); + + fireEvent.mouseEnter(result.getByText('tag1').closest('.euiFlexGroup')!); + + expect(result.getByRole('button').getAttribute('aria-label')).toEqual('Tag Options'); + + fireEvent.mouseLeave(result.getByText('tag1').closest('.euiFlexGroup')!); + + expect(result.queryByRole('button')).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx index eb1e935af9119..32e3a10d68551 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/tags_add_remove.tsx @@ -8,11 +8,22 @@ import React, { Fragment, useCallback, useEffect, useState } from 'react'; import { difference } from 'lodash'; import type { EuiSelectableOption } from '@elastic/eui'; -import { EuiButtonEmpty, EuiIcon, EuiSelectable, EuiWrappingPopover } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHighlight, + EuiIcon, + EuiSelectable, + EuiWrappingPopover, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; import { useUpdateTags } from '../hooks'; +import { TagOptions } from './tag_options'; + interface Props { agentId?: string; agents?: string[] | string; @@ -29,19 +40,13 @@ export const TagsAddRemove: React.FC<Props> = ({ selectedTags, button, onTagsUpdated, -}: { - agentId?: string; - agents?: string[] | string; - allTags: string[]; - selectedTags: string[]; - button: HTMLElement; - onTagsUpdated: () => void; -}) => { +}: Props) => { const labelsFromTags = useCallback( (tags: string[]) => tags.map((tag: string) => ({ label: tag, checked: selectedTags.includes(tag) ? 'on' : undefined, + onFocusBadge: false, })), [selectedTags] ); @@ -49,6 +54,7 @@ export const TagsAddRemove: React.FC<Props> = ({ const [labels, setLabels] = useState<Array<EuiSelectableOption<any>>>(labelsFromTags(allTags)); const [searchValue, setSearchValue] = useState<string | undefined>(undefined); const [isPopoverOpen, setIsPopoverOpen] = useState(true); + const [isTagHovered, setIsTagHovered] = useState<{ [tagName: string]: boolean }>({}); const closePopover = () => setIsPopoverOpen(false); const updateTagsHook = useUpdateTags(); @@ -70,67 +76,88 @@ export const TagsAddRemove: React.FC<Props> = ({ } }; - const setOptions = (newOptions: Array<EuiSelectableOption<any>>) => { - setLabels(newOptions); - - const existingCheckedTags = labels - .filter((option) => option.checked === 'on') - .map((option) => option.label); - const newCheckedTags = newOptions - .filter((option) => option.checked === 'on') - .map((option) => option.label); - const tagsToAdd = difference(newCheckedTags, existingCheckedTags); - const tagsToRemove = difference(existingCheckedTags, newCheckedTags); - - updateTags(tagsToAdd, tagsToRemove); - }; - - return ( - <EuiWrappingPopover - isOpen={isPopoverOpen} - button={button!} - closePopover={closePopover} - anchorPosition="leftUp" - > - <EuiSelectable - aria-label="Add / remove tags" - searchable - searchProps={{ - 'data-test-subj': 'addRemoveTags', - onChange: (value: string) => { - setSearchValue(value); - }, - }} - options={labels} - onChange={(newOptions) => setOptions(newOptions)} - noMatchesMessage={ - <EuiButtonEmpty - color="text" + const renderOption = (option: EuiSelectableOption<any>, search: string) => { + return ( + <EuiFlexGroup + onMouseEnter={() => setIsTagHovered({ ...isTagHovered, [option.label]: true })} + onMouseLeave={() => setIsTagHovered({ ...isTagHovered, [option.label]: false })} + > + <EuiFlexItem> + <EuiHighlight + search={search} onClick={() => { - if (!searchValue) { - return; - } - updateTags([searchValue], []); + const tagsToAdd = option.checked === 'on' ? [] : [option.label]; + const tagsToRemove = option.checked === 'on' ? [option.label] : []; + updateTags(tagsToAdd, tagsToRemove); }} > - <EuiIcon type="plus" />{' '} - <FormattedMessage - id="xpack.fleet.tagsAddRemove.createText" - defaultMessage='Create a new tag "{name}"' - values={{ - name: searchValue, - }} - /> - </EuiButtonEmpty> - } + {option.label} + </EuiHighlight> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <TagOptions + tagName={option.label} + isTagHovered={isTagHovered[option.label]} + onTagsUpdated={onTagsUpdated} + /> + </EuiFlexItem> + </EuiFlexGroup> + ); + }; + + return ( + <> + <EuiWrappingPopover + isOpen={isPopoverOpen} + button={button!} + closePopover={closePopover} + anchorPosition="leftUp" > - {(list, search) => ( - <Fragment> - {search} - {list} - </Fragment> - )} - </EuiSelectable> - </EuiWrappingPopover> + <EuiSelectable + aria-label={i18n.translate('xpack.fleet.tagsAddRemove.selectableTagsLabel', { + defaultMessage: 'Add / remove tags', + })} + searchable + searchProps={{ + 'data-test-subj': 'addRemoveTags', + placeholder: i18n.translate('xpack.fleet.tagsAddRemove.findOrCreatePlaceholder', { + defaultMessage: 'Find or create label...', + }), + onChange: (value: string) => { + setSearchValue(value); + }, + }} + options={labels} + renderOption={renderOption} + noMatchesMessage={ + <EuiButtonEmpty + color="text" + onClick={() => { + if (!searchValue) { + return; + } + updateTags([searchValue], []); + }} + > + <EuiIcon type="plus" />{' '} + <FormattedMessage + id="xpack.fleet.tagsAddRemove.createText" + defaultMessage='Create a new tag "{name}"' + values={{ + name: searchValue, + }} + /> + </EuiButtonEmpty> + } + > + {(list, search) => ( + <Fragment> + {search} + {list} + </Fragment> + )} + </EuiSelectable> + </EuiWrappingPopover> + </> ); }; 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 b55747419b724..9d95455c9636a 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 @@ -632,6 +632,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { Promise.all([fetchData({ refreshTags }), refreshUpgrades()]) } onClickAddAgent={() => setEnrollmentFlyoutState({ isOpen: true })} + onClickAddFleetServer={onClickAddFleetServer} visibleAgents={agents} /> <EuiSpacer size="m" /> diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx index 35353b9aaae3d..7139cabed4f6f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx @@ -69,6 +69,8 @@ describe('AgentApp', () => { enabled: true, isReady: false, refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); @@ -82,6 +84,8 @@ describe('AgentApp', () => { isReady: false, missingRequirements: ['api_keys'], refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); expect(utils.queryByText('MissingESRequirementsPage')).not.toBeNull(); @@ -95,6 +99,8 @@ describe('AgentApp', () => { isReady: false, missingRequirements: ['fleet_server'], refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); expect(utils.queryByText('FleetServerRequirementPage')).not.toBeNull(); @@ -109,6 +115,8 @@ describe('AgentApp', () => { missingRequirements: [], missingOptionalFeatures: ['encrypted_saved_object_encryption_key_required'], refresh: async () => {}, + forceDisplayInstructions: false, + setForceDisplayInstructions: () => {}, }); const { utils } = renderAgentsApp(); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index 78ceb6293d3ce..ed770311ad9e9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -75,6 +75,9 @@ export const AgentsApp: React.FunctionComponent = () => { fleetStatus?.missingRequirements?.length === 1 && fleetStatus.missingRequirements[0] === 'fleet_server'; + const displayInstructions = + fleetStatus.forceDisplayInstructions || hasOnlyFleetServerMissingRequirement; + if ( !hasOnlyFleetServerMissingRequirement && fleetStatus.missingRequirements && @@ -86,7 +89,7 @@ export const AgentsApp: React.FunctionComponent = () => { return <NoAccessPage />; } - const rightColumn = hasOnlyFleetServerMissingRequirement ? ( + const rightColumn = displayInstructions ? ( <> <EuiFlexGroup justifyContent="flexEnd"> <EuiFlexItem grow={false}> @@ -114,7 +117,7 @@ export const AgentsApp: React.FunctionComponent = () => { {fleetServerModalVisible && ( <FleetServerUpgradeModal onClose={onCloseFleetServerModal} /> )} - {hasOnlyFleetServerMissingRequirement ? ( + {displayInstructions ? ( <FleetServerRequirementPage showEnrollmentRecommendation={false} /> ) : ( <AgentListPage /> diff --git a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx index aa43a6b336a7c..7e3b414cf0f6d 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/components/header/deployment_details.component.tsx @@ -69,8 +69,10 @@ export const DeploymentDetails = ({ cloudId, learnMoreUrl, managementUrl }: Prop > <div style={{ width: 450 }}> <Description> - Send data to Elastic from your applications by referencing your deployment and - Elasticsearch information. + {i18n.translate('xpack.fleet.integrations.deploymentDescription', { + defaultMessage: + 'Send data to Elastic from your applications by referencing your deployment.', + })} </Description> <EuiForm component="div"> <EuiFormRow label="Cloud ID" fullWidth> diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx index ff98fb988d04b..e62044b31333a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_card.stories.tsx @@ -69,6 +69,7 @@ export const Installed = ({ width, ...props }: Args) => { install_source: 'registry', install_started_at: '2020-01-01T00:00:00.000Z', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], }; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index 9195e367030dd..a77c45a6b5110 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -34,7 +34,8 @@ import { createIntegrationsTestRendererMock } from '../../../../../../mock'; import { ExperimentalFeaturesService } from '../../../../services'; -ExperimentalFeaturesService.init({ createPackagePolicyMultiPageLayout: false }); +// @ts-ignore this saves us having to define all experimental features +ExperimentalFeaturesService.init({}); import { Detail } from '.'; describe('when on integration detail', () => { diff --git a/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts b/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts index 062d0cee5e886..9e2dc6351ed99 100644 --- a/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts +++ b/x-pack/plugins/fleet/public/hooks/use_agent_enrollment_flyout_data.ts @@ -9,6 +9,7 @@ import { useMemo } from 'react'; import type { AgentPolicy } from '../types'; import { SO_SEARCH_LIMIT } from '../constants'; +import { policyHasFleetServer } from '../services'; import { useGetAgentPolicies } from './use_request'; @@ -33,7 +34,7 @@ export function useAgentEnrollmentFlyoutData(): AgentEnrollmentFlyoutData { const agentPolicies = useMemo(() => { if (!isLoadingAgentPolicies) { - return agentPoliciesData?.items ?? []; + return (agentPoliciesData?.items ?? []).filter((policy) => !policyHasFleetServer(policy)); } return []; }, [isLoadingAgentPolicies, agentPoliciesData?.items]); diff --git a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx index b05df0c619e0a..7319cd9f07be7 100644 --- a/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx +++ b/x-pack/plugins/fleet/public/hooks/use_fleet_status.tsx @@ -23,17 +23,26 @@ interface FleetStatusState { interface FleetStatus extends FleetStatusState { refresh: () => Promise<void>; + + // This flag allows us to opt into displaying the Fleet Server enrollment instructions even if + // a healthy Fleet Server has been detected, so we can delay removing the enrollment UI until + // some user action like clicking a "continue" button + forceDisplayInstructions: boolean; + setForceDisplayInstructions: React.Dispatch<boolean>; } const FleetStatusContext = React.createContext<FleetStatus | undefined>(undefined); export const FleetStatusProvider: React.FC = ({ children }) => { const config = useConfig(); + const [forceDisplayInstructions, setForceDisplayInstructions] = useState(false); + const [state, setState] = useState<FleetStatusState>({ enabled: config.agents.enabled, isLoading: false, isReady: false, }); + const sendGetStatus = useCallback( async function sendGetStatus() { try { @@ -64,7 +73,9 @@ export const FleetStatusProvider: React.FC = ({ children }) => { const refresh = useCallback(() => sendGetStatus(), [sendGetStatus]); return ( - <FleetStatusContext.Provider value={{ ...state, refresh }}> + <FleetStatusContext.Provider + value={{ ...state, refresh, forceDisplayInstructions, setForceDisplayInstructions }} + > {children} </FleetStatusContext.Provider> ); diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index eea3ac5c35f5f..1eb0aed4ad690 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -28,6 +28,7 @@ import { RegistryConnectionError, RegistryError, RegistryResponseError, + PackageFailedVerificationError, } from '.'; type IngestErrorHandler = ( @@ -60,6 +61,9 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { if (error instanceof PackageUnsupportedMediaTypeError) { return 415; // Unsupported Media Type } + if (error instanceof PackageFailedVerificationError) { + return 400; // Bad Request + } if (error instanceof ConcurrentInstallOperationError) { return 409; // Conflict } diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 20f077d6e4e4d..cbfcf5733f736 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -31,6 +31,11 @@ export class RegistryResponseError extends RegistryError { export class PackageNotFoundError extends IngestManagerError {} export class PackageKeyInvalidError extends IngestManagerError {} export class PackageOutdatedError extends IngestManagerError {} +export class PackageFailedVerificationError extends IngestManagerError { + constructor(pkgKey: string) { + super(`${pkgKey} failed signature verification.`); + } +} export class AgentPolicyError extends IngestManagerError {} export class AgentPolicyNotFoundError extends IngestManagerError {} export class AgentNotFoundError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/integration_tests/validate_bundled_packages.test.ts b/x-pack/plugins/fleet/server/integration_tests/validate_bundled_packages.test.ts index fa7f8b2275dad..6b5f3efeae538 100644 --- a/x-pack/plugins/fleet/server/integration_tests/validate_bundled_packages.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/validate_bundled_packages.test.ts @@ -46,10 +46,11 @@ describe('validate bundled packages', () => { bundledPackage.version ); - const packageArchive = await Registry.fetchArchiveBuffer( - bundledPackage.name, - bundledPackage.version - ); + const packageArchive = await Registry.fetchArchiveBuffer({ + pkgName: bundledPackage.name, + pkgVersion: bundledPackage.version, + shouldVerify: false, + }); return { registryPackage, packageArchive }; }) diff --git a/x-pack/plugins/fleet/server/routes/data_streams/get_data_streams_query_metadata.ts b/x-pack/plugins/fleet/server/routes/data_streams/get_data_streams_query_metadata.ts index e2edece439053..44a77a511ca4c 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/get_data_streams_query_metadata.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/get_data_streams_query_metadata.ts @@ -24,10 +24,7 @@ export async function getDataStreamsQueryMetadata({ esClient.search({ size: 1, index: dataStreamName, - sort: { - // @ts-expect-error Type '{ 'event.ingested': string; }' is not assignable to type 'string | string[] | undefined'. - 'event.ingested': 'desc', - }, + sort: 'event.ingested:desc', _source: false, fields: ['event.ingested'], }), @@ -55,9 +52,10 @@ export async function getDataStreamsQueryMetadata({ }), ]); - const maxIngested = new Date( - maxEventIngestedResponse.hits.hits[0]?.fields!['event.ingested'] - ).getTime(); + const maxIngested = + maxEventIngestedResponse.hits.hits[0]?.fields !== undefined + ? new Date(maxEventIngestedResponse.hits.hits[0].fields['event.ingested']).getTime() + : undefined; const namespace = namespaceResponse.terms[0] ?? ''; const dataset = datasetResponse.terms[0] ?? ''; diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index fb64803a7e6d5..bc52fa2e2017a 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -231,6 +231,8 @@ const getSavedObjectTypes = ( enabled: false, type: 'object', }, + verification_status: { type: 'keyword' }, + verification_key_id: { type: 'keyword' }, installed_es: { type: 'nested', properties: { diff --git a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts index ea94823287735..7b9aae17e68ab 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts @@ -5,11 +5,15 @@ * 2.0. */ -import type { ArchivePackage, RegistryPackage } from '../../../../common'; +import type { ArchivePackage, RegistryPackage, PackageVerificationResult } from '../../../types'; import { appContextService } from '../..'; import type { ArchiveEntry } from '.'; +type SharedKeyString = string; + +const sharedKey = ({ name, version }: SharedKey): SharedKeyString => `${name}-${version}`; + const archiveEntryCache: Map<ArchiveEntry['path'], ArchiveEntry['buffer']> = new Map(); export const getArchiveEntry = (key: string) => archiveEntryCache.get(key); export const setArchiveEntry = (key: string, value: Buffer) => archiveEntryCache.set(key, value); @@ -17,11 +21,21 @@ export const hasArchiveEntry = (key: string) => archiveEntryCache.has(key); export const clearArchiveEntries = () => archiveEntryCache.clear(); export const deleteArchiveEntry = (key: string) => archiveEntryCache.delete(key); +const verificationResultCache: Map<SharedKeyString, PackageVerificationResult> = new Map(); +export const getVerificationResult = (key: SharedKey) => + verificationResultCache.get(sharedKey(key)); +export const setVerificationResult = (key: SharedKey, value: PackageVerificationResult) => + verificationResultCache.set(sharedKey(key), value); +export const hasVerificationResult = (key: SharedKey) => + verificationResultCache.has(sharedKey(key)); +export const clearVerificationResults = () => verificationResultCache.clear(); +export const deleteVerificationResult = (key: SharedKey) => + verificationResultCache.delete(sharedKey(key)); + export interface SharedKey { name: string; version: string; } -type SharedKeyString = string; const archiveFilelistCache: Map<SharedKeyString, string[]> = new Map(); export const getArchiveFilelist = (keyArgs: SharedKey) => @@ -38,7 +52,6 @@ export const deleteArchiveFilelist = (keyArgs: SharedKey) => archiveFilelistCache.delete(sharedKey(keyArgs)); const packageInfoCache: Map<SharedKeyString, ArchivePackage | RegistryPackage> = new Map(); -const sharedKey = ({ name, version }: SharedKey) => `${name}-${version}`; export const getPackageInfo = (args: SharedKey) => { return packageInfoCache.get(sharedKey(args)); diff --git a/x-pack/plugins/fleet/server/services/epm/archive/index.ts b/x-pack/plugins/fleet/server/services/epm/archive/index.ts index e7e43d3d932b7..4d70c62bd55d3 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AssetParts, InstallSource } from '../../../../common/types'; +import type { AssetParts } from '../../../../common/types'; import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors'; import { @@ -35,13 +35,11 @@ export async function unpackBufferToCache({ version, contentType, archiveBuffer, - installSource, }: { name: string; version: string; contentType: string; archiveBuffer: Buffer; - installSource: InstallSource; }): Promise<string[]> { // Make sure any buffers from previous installations from registry or upload are deleted first clearPackageFileCache({ name, version }); diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts index 782af2860d2e3..1dc4039a7c2a7 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.test.ts @@ -102,7 +102,7 @@ function getTest( method: mocks.packageClient.getRegistryPackage.bind(mocks.packageClient), args: ['package name', '8.0.0'], spy: jest.spyOn(epmRegistry, 'getRegistryPackage'), - spyArgs: ['package name', '8.0.0'], + spyArgs: ['package name', '8.0.0', undefined], spyResponse: { packageInfo: { name: 'getRegistryPackage test' }, paths: ['/some/test/path'], diff --git a/x-pack/plugins/fleet/server/services/epm/package_service.ts b/x-pack/plugins/fleet/server/services/epm/package_service.ts index e16d4954f0b9d..c43386acdbdf4 100644 --- a/x-pack/plugins/fleet/server/services/epm/package_service.ts +++ b/x-pack/plugins/fleet/server/services/epm/package_service.ts @@ -120,9 +120,13 @@ class PackageClientImpl implements PackageClient { return fetchFindLatestPackageOrThrow(packageName); } - public async getRegistryPackage(packageName: string, packageVersion: string) { + public async getRegistryPackage( + packageName: string, + packageVersion: string, + options?: Parameters<typeof getRegistryPackage>['2'] + ) { await this.#runPreflight(); - return getRegistryPackage(packageName, packageVersion); + return getRegistryPackage(packageName, packageVersion, options); } public async reinstallEsAssets( diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 57e293257d176..ec7e65c2b3875 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -19,9 +19,16 @@ import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT, } from '../../../../common'; -import type { InstallablePackage, InstallSource, PackageAssetReference } from '../../../../common'; import { PACKAGES_SAVED_OBJECT_TYPE, FLEET_INSTALL_FORMAT_VERSION } from '../../../constants'; -import type { AssetReference, Installation, InstallType } from '../../../types'; +import type { + AssetReference, + Installation, + InstallType, + InstallablePackage, + InstallSource, + PackageAssetReference, + PackageVerificationResult, +} from '../../../types'; import { prepareToInstallTemplates } from '../elasticsearch/template/install'; import { removeLegacyTemplates } from '../elasticsearch/template/remove_legacy'; import { @@ -39,7 +46,7 @@ import { saveArchiveEntries } from '../archive/storage'; import { ConcurrentInstallOperationError } from '../../../errors'; import { packagePolicyService } from '../..'; -import { createInstallation, updateEsAssetReferences } from './install'; +import { createInstallation, updateEsAssetReferences, restartInstallation } from './install'; import { withPackageSpan } from './utils'; // this is only exported for testing @@ -56,6 +63,7 @@ export async function _installPackage({ installType, installSource, spaceId, + verificationResult, }: { savedObjectsClient: SavedObjectsClientContract; savedObjectsImporter: Pick<SavedObjectsImporter, 'import' | 'resolveImportErrors'>; @@ -67,6 +75,7 @@ export async function _installPackage({ installType: InstallType; installSource: InstallSource; spaceId: string; + verificationResult?: PackageVerificationResult; }): Promise<AssetReference[]> { const { name: pkgName, version: pkgVersion } = packageInfo; @@ -88,11 +97,12 @@ export async function _installPackage({ } else { // if no installation is running, or the installation has been running longer than MAX_TIME_COMPLETE_INSTALL // (it might be stuck) update the saved object and proceed - await savedObjectsClient.update<Installation>(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { - install_version: pkgVersion, - install_status: 'installing', - install_started_at: new Date().toISOString(), - install_source: installSource, + await restartInstallation({ + savedObjectsClient, + pkgName, + pkgVersion, + installSource, + verificationResult, }); } } else { @@ -101,6 +111,7 @@ export async function _installPackage({ packageInfo, installSource, spaceId, + verificationResult, }); } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index acd5761919a16..461b7241fe167 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -22,7 +22,11 @@ import type { GetCategoriesRequest, } from '../../../../common/types'; import type { Installation, PackageInfo } from '../../../types'; -import { IngestManagerError, PackageNotFoundError } from '../../../errors'; +import { + IngestManagerError, + PackageFailedVerificationError, + PackageNotFoundError, +} from '../../../errors'; import { appContextService } from '../..'; import * as Registry from '../registry'; import { getEsPackage } from '../archive/storage'; @@ -268,8 +272,10 @@ export async function getPackageFromSource(options: { try { res = await Registry.getRegistryPackage(pkgName, pkgVersion); logger.debug(`retrieved installed package ${pkgName}-${pkgVersion} from registry`); - // TODO: add to cache and storage here? } catch (error) { + if (error instanceof PackageFailedVerificationError) { + throw error; + } // treating this is a 404 as no status code returned // in the unlikely event its missing from cache, storage, and never installed from registry } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts index e67f59aaba55b..6c4211d8cf2fe 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get_install_type.test.ts @@ -30,6 +30,7 @@ const mockInstallation: SavedObject<Installation> = { install_started_at: new Date().toISOString(), install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, }; const mockInstallationUpdateFail: SavedObject<Installation> = { @@ -50,6 +51,7 @@ const mockInstallationUpdateFail: SavedObject<Installation> = { install_started_at: new Date().toISOString(), install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, }; 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 index 76cf09b0cad06..ac53717bb2b1a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.test.ts @@ -59,6 +59,7 @@ jest.mock('../archive', () => { ), unpackBufferToCache: jest.fn(), setPackageInfo: jest.fn(), + deleteVerificationResult: jest.fn(), }; }); @@ -205,6 +206,7 @@ describe('install', () => { }); it('should install from bundled package if one exists', async () => { + (install._installPackage as jest.Mock).mockResolvedValue({}); mockGetBundledPackages.mockResolvedValue([ { name: 'test_package', @@ -213,7 +215,7 @@ describe('install', () => { }, ]); - await installPackage({ + const response = await installPackage({ spaceId: DEFAULT_SPACE_ID, installSource: 'registry', pkgkey: 'test_package-1.0.0', @@ -221,6 +223,8 @@ describe('install', () => { esClient: {} as ElasticsearchClient, }); + expect(response.error).toBeUndefined(); + expect(install._installPackage).toHaveBeenCalledWith( expect.objectContaining({ installSource: 'upload' }) ); 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 90a41add5f184..c5e5f968c45c7 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -22,24 +22,30 @@ import pRetry from 'p-retry'; import { FLEET_INSTALL_FORMAT_VERSION } from '../../../constants/fleet_es_assets'; import { generateESIndexPatterns } from '../elasticsearch/template/template'; + import type { BulkInstallPackageInfo, EpmPackageInstallStatus, + EsAssetReference, InstallablePackage, + Installation, + InstallResult, InstallSource, -} from '../../../../common'; + InstallType, + KibanaAssetType, + PackageVerificationResult, +} from '../../../types'; import { AUTO_UPGRADE_POLICIES_PACKAGES } from '../../../../common'; import { IngestManagerError, PackageOutdatedError } from '../../../errors'; import { PACKAGES_SAVED_OBJECT_TYPE, MAX_TIME_COMPLETE_INSTALL } from '../../../constants'; -import type { KibanaAssetType } from '../../../types'; import { licenseService } from '../..'; -import type { Installation, EsAssetReference, InstallType, InstallResult } from '../../../types'; import { appContextService } from '../../app_context'; import * as Registry from '../registry'; import { setPackageInfo, generatePackageInfoFromArchiveBuffer, unpackBufferToCache, + deleteVerificationResult, } from '../archive'; import { toAssetReference } from '../kibana/assets/install'; import type { ArchiveAsset } from '../kibana/assets/install'; @@ -47,6 +53,8 @@ import type { ArchiveAsset } from '../kibana/assets/install'; import type { PackageUpdateEvent } from '../../upgrade_sender'; import { sendTelemetryEvents, UpdateEventType } from '../../upgrade_sender'; +import { formatVerificationResultForSO } from './package_verification'; + import { getInstallation, getInstallationObject } from '.'; import { removeInstallation } from './remove'; import { getPackageSavedObjects } from './get'; @@ -277,11 +285,11 @@ async function installPackageFromRegistry({ }); // get latest package version and requested version in parallel for performance - const [latestPackage, { paths, packageInfo }] = await Promise.all([ + const [latestPackage, { paths, packageInfo, verificationResult }] = await Promise.all([ Registry.fetchFindLatestPackageOrThrow(pkgName, { ignoreConstraints, }), - Registry.getRegistryPackage(pkgName, pkgVersion), + Registry.getRegistryPackage(pkgName, pkgVersion, { ignoreUnverified: force }), ]); // let the user install if using the force flag or needing to reinstall or install a previous version due to failed update @@ -352,6 +360,7 @@ async function installPackageFromRegistry({ packageInfo, installType, spaceId, + verificationResult, installSource: 'registry', }) .then(async (assets) => { @@ -435,10 +444,11 @@ async function installPackageByUpload({ telemetryEvent.currentVersion = installedPkg?.attributes.version || 'not_installed'; const installSource = 'upload'; + // as we do not verify uploaded packages, we must invalidate the verification cache + deleteVerificationResult(packageInfo); const paths = await unpackBufferToCache({ name: packageInfo.name, version: packageInfo.version, - installSource, archiveBuffer, contentType, }); @@ -580,13 +590,41 @@ export const updateInstallStatus = async ({ }); }; +export async function restartInstallation(options: { + savedObjectsClient: SavedObjectsClientContract; + pkgName: string; + pkgVersion: string; + installSource: InstallSource; + verificationResult?: PackageVerificationResult; +}) { + const { savedObjectsClient, pkgVersion, pkgName, installSource, verificationResult } = options; + + let savedObjectUpdate: Partial<Installation> = { + install_version: pkgVersion, + install_status: 'installing', + install_started_at: new Date().toISOString(), + install_source: installSource, + }; + + if (verificationResult) { + savedObjectUpdate = { + ...savedObjectUpdate, + verification_key_id: null, // unset any previous verification key id + ...formatVerificationResultForSO(verificationResult), + }; + } + + await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, savedObjectUpdate); +} + export async function createInstallation(options: { savedObjectsClient: SavedObjectsClientContract; packageInfo: InstallablePackage; installSource: InstallSource; spaceId: string; + verificationResult?: PackageVerificationResult; }) { - const { savedObjectsClient, packageInfo, installSource } = options; + const { savedObjectsClient, packageInfo, installSource, verificationResult } = options; const { name: pkgName, version: pkgVersion } = packageInfo; const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams); @@ -599,23 +637,30 @@ export async function createInstallation(options: { ? true : undefined; + let savedObject: Installation = { + installed_kibana: [], + installed_kibana_space_id: options.spaceId, + installed_es: [], + package_assets: [], + es_index_patterns: toSaveESIndexPatterns, + name: pkgName, + version: pkgVersion, + install_version: pkgVersion, + install_status: 'installing', + install_started_at: new Date().toISOString(), + install_source: installSource, + install_format_schema_version: FLEET_INSTALL_FORMAT_VERSION, + keep_policies_up_to_date: defaultKeepPoliciesUpToDate, + verification_status: 'unknown', + }; + + if (verificationResult) { + savedObject = { ...savedObject, ...formatVerificationResultForSO(verificationResult) }; + } + const created = await savedObjectsClient.create<Installation>( PACKAGES_SAVED_OBJECT_TYPE, - { - installed_kibana: [], - installed_kibana_space_id: options.spaceId, - installed_es: [], - package_assets: [], - es_index_patterns: toSaveESIndexPatterns, - name: pkgName, - version: pkgVersion, - install_version: pkgVersion, - install_status: 'installing', - install_started_at: new Date().toISOString(), - install_source: installSource, - install_format_schema_version: FLEET_INSTALL_FORMAT_VERSION, - keep_policies_up_to_date: defaultKeepPoliciesUpToDate, - }, + savedObject, { id: pkgName, overwrite: true } ); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts b/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts index b7114cbd7b449..0e39cefaf1169 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/package_verification.ts @@ -10,7 +10,18 @@ import { readFile } from 'fs/promises'; import * as openpgp from 'openpgp'; import type { Logger } from '@kbn/logging'; +import type { PackageVerificationResult } from '../../../types'; + +import * as Registry from '../registry'; + import { appContextService } from '../../app_context'; +import type { Installation } from '../../../types'; + +interface VerificationResult { + isVerified: boolean; + keyId: string; +} + let cachedKey: openpgp.Key | undefined | null = null; export async function getGpgKeyOrUndefined(): Promise<openpgp.Key | undefined> { @@ -46,28 +57,63 @@ export async function _readGpgKey(): Promise<openpgp.Key | undefined> { return key; } -export interface PackageVerificationResult { - isVerified: boolean; - keyId: string; +export async function verifyPackageArchiveSignature({ + pkgName, + pkgVersion, + pkgArchiveBuffer, + logger, +}: { + pkgName: string; + pkgVersion: string; + pkgArchiveBuffer: Buffer; + logger: Logger; +}): Promise<PackageVerificationResult> { + const verificationKey = await getGpgKeyOrUndefined(); + const result: PackageVerificationResult = { verificationStatus: 'unknown' }; + if (!verificationKey) { + logger.warn(`Not performing package verification as no local verification key found`); + return result; + } + const pkgArchiveSignature = await Registry.getPackageArchiveSignatureOrUndefined({ + pkgName, + pkgVersion, + logger, + }); + + if (!pkgArchiveSignature) { + logger.warn( + `Package ${pkgName}-${pkgVersion} has no corresponding signature. Skipping verification.` + ); + return result; + } + + const { isVerified, keyId } = await _verifyPackageSignature({ + pkgArchiveBuffer, + pkgArchiveSignature, + verificationKey, + logger, + }); + + return { verificationStatus: isVerified ? 'verified' : 'unverified', verificationKeyId: keyId }; } -export async function verifyPackageSignature({ - pkgZipBuffer, - pkgZipSignature, +async function _verifyPackageSignature({ + pkgArchiveBuffer, + pkgArchiveSignature, verificationKey, logger, }: { - pkgZipBuffer: Buffer; - pkgZipSignature: string; + pkgArchiveBuffer: Buffer; + pkgArchiveSignature: string; verificationKey: openpgp.Key; logger: Logger; -}): Promise<PackageVerificationResult> { +}): Promise<VerificationResult> { const signature = await openpgp.readSignature({ - armoredSignature: pkgZipSignature, + armoredSignature: pkgArchiveSignature, }); const message = await openpgp.createMessage({ - binary: pkgZipBuffer, + binary: pkgArchiveBuffer, }); const verificationResult = await openpgp.verify({ @@ -86,3 +132,22 @@ export async function verifyPackageSignature({ } return { isVerified, keyId: verificationKey.getKeyID().toHex() }; } + +type InstallationVerificationKeys = Pick< + Installation, + 'verification_status' | 'verification_key_id' +>; + +export function formatVerificationResultForSO( + verificationResult: PackageVerificationResult +): InstallationVerificationKeys { + const verification: InstallationVerificationKeys = { + verification_status: verificationResult.verificationStatus, + }; + + if (verificationResult.verificationKeyId) { + verification.verification_key_id = verificationResult.verificationKeyId; + } + + return verification; +} diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts index 1074e975d3f6f..6d0ab10e41d98 100644 --- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts @@ -11,6 +11,7 @@ import mime from 'mime-types'; import semverGte from 'semver/functions/gte'; import type { Response } from 'node-fetch'; +import type { Logger } from '@kbn/logging'; import { splitPkgKey as split } from '../../../../common'; @@ -19,26 +20,34 @@ import type { AssetsGroupedByServiceByType, CategoryId, CategorySummaryList, - InstallSource, RegistryPackage, RegistrySearchResults, GetCategoriesRequest, + PackageVerificationResult, } from '../../../types'; import { getArchiveFilelist, getPathParts, unpackBufferToCache, + setVerificationResult, + getVerificationResult, getPackageInfo, setPackageInfo, } from '../archive'; -import { streamToBuffer } from '../streams'; +import { streamToBuffer, streamToString } from '../streams'; import { appContextService } from '../..'; -import { PackageNotFoundError, PackageCacheError, RegistryResponseError } from '../../../errors'; +import { + PackageNotFoundError, + RegistryResponseError, + PackageFailedVerificationError, +} from '../../../errors'; import { getBundledPackageByName } from '../packages/bundled_packages'; import { withPackageSpan } from '../packages/utils'; +import { verifyPackageArchiveSignature } from '../packages/package_verification'; + import { fetchUrl, getResponse, getResponseStream } from './requests'; import { getRegistryUrl } from './registry_url'; @@ -224,20 +233,37 @@ export async function getInfo(name: string, version: string) { export async function getRegistryPackage( name: string, - version: string -): Promise<{ paths: string[]; packageInfo: RegistryPackage }> { - const installSource = 'registry'; + version: string, + options?: { ignoreUnverified?: boolean } +): Promise<{ + paths: string[]; + packageInfo: RegistryPackage; + verificationResult?: PackageVerificationResult; +}> { + const verifyPackage = appContextService.getExperimentalFeatures().packageVerification; let paths = getArchiveFilelist({ name, version }); + let verificationResult = verifyPackage ? getVerificationResult({ name, version }) : undefined; if (!paths || paths.length === 0) { - const { archiveBuffer, archivePath } = await withPackageSpan( - 'Fetch package archive from registry', - () => fetchArchiveBuffer(name, version) + const { + archiveBuffer, + archivePath, + verificationResult: latestVerificationResult, + } = await withPackageSpan('Fetch package archive from registry', () => + fetchArchiveBuffer({ + pkgName: name, + pkgVersion: version, + shouldVerify: verifyPackage, + ignoreUnverified: options?.ignoreUnverified, + }) ); + if (latestVerificationResult) { + verificationResult = latestVerificationResult; + setVerificationResult({ name, version }, latestVerificationResult); + } paths = await withPackageSpan('Unpack archive', () => unpackBufferToCache({ name, version, - installSource, archiveBuffer, contentType: ensureContentType(archivePath), }) @@ -245,7 +271,7 @@ export async function getRegistryPackage( } const packageInfo = await getInfo(name, version); - return { paths, packageInfo }; + return { paths, packageInfo, verificationResult }; } function ensureContentType(archivePath: string) { @@ -256,32 +282,67 @@ function ensureContentType(archivePath: string) { return contentType; } -export async function ensureCachedArchiveInfo( - name: string, - version: string, - installSource: InstallSource = 'registry' -) { - const paths = getArchiveFilelist({ name, version }); - if (!paths || paths.length === 0) { - if (installSource === 'registry') { - await getRegistryPackage(name, version); - } else { - throw new PackageCacheError( - `Package ${name}-${version} not cached. If it was uploaded, try uninstalling and reinstalling manually.` - ); +export async function fetchArchiveBuffer({ + pkgName, + pkgVersion, + shouldVerify, + ignoreUnverified = false, +}: { + pkgName: string; + pkgVersion: string; + shouldVerify: boolean; + ignoreUnverified?: boolean; +}): Promise<{ + archiveBuffer: Buffer; + archivePath: string; + verificationResult?: PackageVerificationResult; +}> { + const logger = appContextService.getLogger(); + const { download: archivePath } = await getInfo(pkgName, pkgVersion); + const archiveUrl = `${getRegistryUrl()}${archivePath}`; + const archiveBuffer = await getResponseStream(archiveUrl).then(streamToBuffer); + if (shouldVerify) { + const verificationResult = await verifyPackageArchiveSignature({ + pkgName, + pkgVersion, + pkgArchiveBuffer: archiveBuffer, + logger, + }); + + if (verificationResult.verificationStatus === 'unverified' && !ignoreUnverified) { + throw new PackageFailedVerificationError(`${pkgName}-${pkgVersion}`); } + return { archiveBuffer, archivePath, verificationResult }; } + return { archiveBuffer, archivePath }; } -export async function fetchArchiveBuffer( - pkgName: string, - pkgVersion: string -): Promise<{ archiveBuffer: Buffer; archivePath: string }> { - const { download: archivePath } = await getInfo(pkgName, pkgVersion); - const archiveUrl = `${getRegistryUrl()}${archivePath}`; - const archiveBuffer = await getResponseStream(archiveUrl).then(streamToBuffer); +export async function getPackageArchiveSignatureOrUndefined({ + pkgName, + pkgVersion, + logger, +}: { + pkgName: string; + pkgVersion: string; + logger: Logger; +}): Promise<string | undefined> { + const { signature_path: signaturePath } = await getInfo(pkgName, pkgVersion); + + if (!signaturePath) { + logger.debug( + `Package ${pkgName}-${pkgVersion} does not have a signature_path, verification will not be possible.` + ); + return undefined; + } - return { archiveBuffer, archivePath }; + try { + const { body } = await fetchFile(signaturePath); + + return streamToString(body); + } catch (e) { + logger.error(`Error retrieving package signature at '${signaturePath}' : ${e}`); + return undefined; + } } export function groupPathsByService(paths: string[]): AssetsGroupedByServiceByType { diff --git a/x-pack/plugins/fleet/server/types/index.tsx b/x-pack/plugins/fleet/server/types/index.tsx index 1d83d4011115c..2ddf79ff39ef4 100644 --- a/x-pack/plugins/fleet/server/types/index.tsx +++ b/x-pack/plugins/fleet/server/types/index.tsx @@ -77,6 +77,9 @@ export type { DownloadSourceBase, DownloadSource, DownloadSourceAttributes, + PackageVerificationStatus, + BulkInstallPackageInfo, + PackageAssetReference, } from '../../common'; export { ElasticsearchAssetType, @@ -96,6 +99,12 @@ export interface BulkActionResult { error?: Error; } +import type { PackageVerificationStatus } from '../../common'; +export interface PackageVerificationResult { + verificationKeyId?: string; + verificationStatus: PackageVerificationStatus; +} + export * from './models'; export * from './rest_spec'; export * from './extensions'; diff --git a/x-pack/plugins/index_lifecycle_management/server/services/license.ts b/x-pack/plugins/index_lifecycle_management/server/services/license.ts index e51ecac56327e..4b3b1331ae444 100644 --- a/x-pack/plugins/index_lifecycle_management/server/services/license.ts +++ b/x-pack/plugins/index_lifecycle_management/server/services/license.ts @@ -57,12 +57,12 @@ export class License { }); } - guardApiRoute(handler: RequestHandler) { + guardApiRoute<P, Q, B>(handler: RequestHandler<P, Q, B>) { const license = this; return function licenseCheck( ctx: RequestHandlerContext, - request: KibanaRequest, + request: KibanaRequest<P, Q, B>, response: KibanaResponseFactory ) { const licenseStatus = license.getStatus(); diff --git a/x-pack/plugins/index_management/server/lib/fetch_indices.ts b/x-pack/plugins/index_management/server/lib/fetch_indices.ts index 8635b9fd4f196..0ec3a8f1713a4 100644 --- a/x-pack/plugins/index_management/server/lib/fetch_indices.ts +++ b/x-pack/plugins/index_management/server/lib/fetch_indices.ts @@ -56,8 +56,8 @@ async function fetchIndicesCall( uuid: indexStats?.uuid, primary: indexData.settings?.index?.number_of_shards, replica: indexData.settings?.index?.number_of_replicas, - documents: indexStats?.total?.docs?.count ?? 0, - documents_deleted: indexStats?.total?.docs?.deleted ?? 0, + documents: indexStats?.primaries?.docs?.count ?? 0, + documents_deleted: indexStats?.primaries?.docs?.deleted ?? 0, size: new ByteSizeValue(indexStats?.total?.store?.size_in_bytes ?? 0).toString(), primary_size: new ByteSizeValue(indexStats?.primaries?.store?.size_in_bytes ?? 0).toString(), // @ts-expect-error diff --git a/x-pack/plugins/index_management/server/test/helpers/indices_fixtures.ts b/x-pack/plugins/index_management/server/test/helpers/indices_fixtures.ts index bffbf858d96a0..0dd7d3d0b70db 100644 --- a/x-pack/plugins/index_management/server/test/helpers/indices_fixtures.ts +++ b/x-pack/plugins/index_management/server/test/helpers/indices_fixtures.ts @@ -23,8 +23,8 @@ export const createTestIndexStats = (index?: Partial<IndicesStatsIndicesStats>) health: 'green', status: 'open', uuid: 'test_index', - total: { docs: { count: 1, deleted: 0 }, store: { size_in_bytes: 100 } }, - primaries: { store: { size_in_bytes: 100 } }, + total: { docs: { count: 111, deleted: 110 }, store: { size_in_bytes: 100 } }, + primaries: { docs: { count: 1, deleted: 0 }, store: { size_in_bytes: 100 } }, ...index, }; }; diff --git a/x-pack/plugins/infra/common/inventory_models/container/index.ts b/x-pack/plugins/infra/common/inventory_models/container/index.ts index ab3e65c4bae16..9e9ee6a0a3cc4 100644 --- a/x-pack/plugins/infra/common/inventory_models/container/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/container/index.ts @@ -37,6 +37,9 @@ export const container: InventoryModel = { 'containerNetworkTraffic', 'containerDiskIOBytes', 'containerDiskIOOps', + 'containerK8sOverview', + 'containerK8sCpuUsage', + 'containerK8sMemoryUsage', ], tooltipMetrics: ['cpu', 'memory', 'rx', 'tx'], }; diff --git a/x-pack/plugins/infra/common/inventory_models/container/layout.tsx b/x-pack/plugins/infra/common/inventory_models/container/layout.tsx index 11a84abe1bf67..ec1260ab6100a 100644 --- a/x-pack/plugins/infra/common/inventory_models/container/layout.tsx +++ b/x-pack/plugins/infra/common/inventory_models/container/layout.tsx @@ -43,6 +43,35 @@ export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPr metrics={metrics} onChangeRangeTime={onChangeRangeTime} > + <SubSection id="containerK8sOverview"> + <GaugesSectionVis + seriesOverrides={{ + cpu: { + name: i18n.translate( + 'xpack.infra.metricDetailPage.containerMetricsLayout.overviewSection.cpuUsageSeriesLabel', + { + defaultMessage: 'CPU Usage', + } + ), + color: theme.eui.euiColorFullShade, + formatter: 'percent', + gaugeMax: 1, + }, + memory: { + name: i18n.translate( + 'xpack.infra.metricDetailPage.containerMetricsLayout.overviewSection.memoryUsageSeriesLabel', + { + defaultMessage: 'Memory Usage', + } + ), + color: theme.eui.euiColorFullShade, + formatter: 'percent', + gaugeMax: 1, + }, + }} + /> + </SubSection> + <SubSection id="containerOverview"> <GaugesSectionVis seriesOverrides={{ @@ -93,6 +122,42 @@ export const Layout = withTheme(({ metrics, onChangeRangeTime, theme }: LayoutPr }} /> </SubSection> + <SubSection + id="containerK8sCpuUsage" + label={i18n.translate( + 'xpack.infra.metricDetailPage.containerMetricsLayout.cpuUsageSection.sectionLabel', + { + defaultMessage: 'CPU Usage', + } + )} + > + <ChartSectionVis + stacked={true} + type="area" + formatter="percent" + seriesOverrides={{ + cpu: { color: theme.eui.euiColorVis1 }, + }} + /> + </SubSection> + <SubSection + id="containerK8sMemoryUsage" + label={i18n.translate( + 'xpack.infra.metricDetailPage.containerMetricsLayout.memoryUsageSection.sectionLabel', + { + defaultMessage: 'Memory Usage', + } + )} + > + <ChartSectionVis + stacked={true} + type="area" + formatter="percent" + seriesOverrides={{ + memory: { color: theme.eui.euiColorVis1 }, + }} + /> + </SubSection> <SubSection id="containerCpuUsage" label={i18n.translate( diff --git a/x-pack/plugins/infra/common/inventory_models/container/metrics/index.ts b/x-pack/plugins/infra/common/inventory_models/container/metrics/index.ts index daa74cb8ae265..053f6565333c0 100644 --- a/x-pack/plugins/infra/common/inventory_models/container/metrics/index.ts +++ b/x-pack/plugins/infra/common/inventory_models/container/metrics/index.ts @@ -18,6 +18,9 @@ import { containerDiskIOOps } from './tsvb/container_diskio_ops'; import { containerDiskIOBytes } from './tsvb/container_disk_io_bytes'; import { containerMemory } from './tsvb/container_memory'; import { containerNetworkTraffic } from './tsvb/container_network_traffic'; +import { containerK8sOverview } from './tsvb/container_k8s_overview'; +import { containerK8sCpuUsage } from './tsvb/container_k8s_cpu_usage'; +import { containerK8sMemoryUsage } from './tsvb/container_k8s_memory_usage'; export const metrics: InventoryMetrics = { tsvb: { @@ -28,6 +31,9 @@ export const metrics: InventoryMetrics = { containerDiskIOBytes, containerNetworkTraffic, containerMemory, + containerK8sCpuUsage, + containerK8sOverview, + containerK8sMemoryUsage, }, snapshot: { cpu, memory, rx, tx }, defaultSnapshot: 'cpu', diff --git a/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_cpu_usage.ts b/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_cpu_usage.ts new file mode 100644 index 0000000000000..67372f50d750b --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_cpu_usage.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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types'; + +export const containerK8sCpuUsage: TSVBMetricModelCreator = ( + timeField, + indexPattern, + interval +): TSVBMetricModel => ({ + id: 'containerK8sCpuUsage', + requires: ['kubernetes.container'], + index_pattern: indexPattern, + interval, + time_field: timeField, + type: 'timeseries', + series: [ + { + id: 'cpu', + split_mode: 'everything', + metrics: [ + { + field: 'kubernetes.container.cpu.usage.limit.pct', + id: 'avg-cpu', + type: 'avg', + }, + ], + }, + ], +}); diff --git a/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_memory_usage.ts b/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_memory_usage.ts new file mode 100644 index 0000000000000..066d993817b75 --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_memory_usage.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 { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types'; + +export const containerK8sMemoryUsage: TSVBMetricModelCreator = ( + timeField, + indexPattern, + interval +): TSVBMetricModel => ({ + id: 'containerK8sMemoryUsage', + requires: ['kubernetes.container'], + index_pattern: indexPattern, + interval, + time_field: timeField, + type: 'timeseries', + series: [ + { + id: 'memory', + split_mode: 'everything', + metrics: [ + { + field: 'kubernetes.container.memory.usage.limit.pct', + id: 'avg-memory', + type: 'avg', + }, + ], + }, + ], +}); diff --git a/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_overview.ts b/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_overview.ts new file mode 100644 index 0000000000000..470704140efb4 --- /dev/null +++ b/x-pack/plugins/infra/common/inventory_models/container/metrics/tsvb/container_k8s_overview.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { TSVBMetricModelCreator, TSVBMetricModel } from '../../../types'; + +export const containerK8sOverview: TSVBMetricModelCreator = ( + timeField, + indexPattern, + interval +): TSVBMetricModel => ({ + id: 'containerK8sOverview', + requires: ['kubernetes.container'], + index_pattern: indexPattern, + interval, + time_field: timeField, + type: 'timeseries', + series: [ + { + id: 'cpu', + split_mode: 'everything', + metrics: [ + { + field: 'kubernetes.container.cpu.usage.limit.pct', + id: 'avg-cpu-total', + type: 'avg', + }, + ], + }, + { + id: 'memory', + split_mode: 'everything', + metrics: [ + { + field: 'kubernetes.container.memory.usage.limit.pct', + id: 'avg-memory-total', + type: 'avg', + }, + ], + }, + ], +}); diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/rx.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/rx.ts index a050160789f96..d00d3aaa942cd 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/rx.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/rx.ts @@ -5,9 +5,38 @@ * 2.0. */ -import { networkTrafficWithInterfaces } from '../../../shared/metrics/snapshot/network_traffic_with_interfaces'; -export const rx = networkTrafficWithInterfaces( - 'rx', - 'system.network.in.bytes', - 'system.network.name' -); +import { MetricsUIAggregation } from '../../../types'; +export const rx: MetricsUIAggregation = { + rx_avg: { + avg: { + field: 'host.network.ingress.bytes', + }, + }, + rx_period: { + filter: { + exists: { + field: 'host.network.ingress.bytes', + }, + }, + aggs: { + period: { + max: { + field: 'metricset.period', + }, + }, + }, + }, + rx: { + bucket_script: { + buckets_path: { + value: 'rx_avg', + period: 'rx_period>period', + }, + script: { + source: 'params.value / (params.period / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, +}; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/tx.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/tx.ts index e75e2110e832a..1d0ad172749b7 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/tx.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/snapshot/tx.ts @@ -5,9 +5,38 @@ * 2.0. */ -import { networkTrafficWithInterfaces } from '../../../shared/metrics/snapshot/network_traffic_with_interfaces'; -export const tx = networkTrafficWithInterfaces( - 'tx', - 'system.network.out.bytes', - 'system.network.name' -); +import { MetricsUIAggregation } from '../../../types'; +export const tx: MetricsUIAggregation = { + tx_avg: { + avg: { + field: 'host.network.egress.bytes', + }, + }, + tx_period: { + filter: { + exists: { + field: 'host.network.egress.bytes', + }, + }, + aggs: { + period: { + max: { + field: 'metricset.period', + }, + }, + }, + }, + tx: { + bucket_script: { + buckets_path: { + value: 'tx_avg', + period: 'tx_period>period', + }, + script: { + source: 'params.value / (params.period / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, +}; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts index ec8c0937ce0ea..b3dfc5e91ba0a 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_network_traffic.ts @@ -23,50 +23,46 @@ export const hostNetworkTraffic: TSVBMetricModelCreator = ( id: 'tx', metrics: [ { - field: 'system.network.out.bytes', - id: 'max-net-out', - type: 'max', + field: 'host.network.egress.bytes', + id: 'avg-net-out', + type: 'avg', }, { - field: 'max-net-out', - id: 'deriv-max-net-out', - type: 'derivative', - unit: '1s', + id: 'max-period', + type: 'max', + field: 'metricset.period', }, { - id: 'posonly-deriv-max-net-out', + id: '3216b170-f192-11ec-a8e3-dd984b7213e2', type: 'calculation', - variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-net-out' }], - script: 'params.rate > 0.0 ? params.rate : 0.0', - }, - { - function: 'sum', - id: 'seriesagg-sum', - type: 'series_agg', + variables: [ + { + id: '34e64c30-f192-11ec-a8e3-dd984b7213e2', + name: 'value', + field: 'avg-net-out', + }, + { + id: '3886cb80-f192-11ec-a8e3-dd984b7213e2', + name: 'period', + field: 'max-period', + }, + ], + script: 'params.value / (params.period / 1000)', }, ], - split_mode: 'terms', - terms_field: 'system.network.name', + filter: { + language: 'kuery', + query: 'host.network.egress.bytes : * ', + }, + split_mode: 'everything', }, { id: 'rx', metrics: [ { - field: 'system.network.in.bytes', - id: 'max-net-in', - type: 'max', - }, - { - field: 'max-net-in', - id: 'deriv-max-net-in', - type: 'derivative', - unit: '1s', - }, - { - id: 'posonly-deriv-max-net-in', - type: 'calculation', - variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-net-in' }], - script: 'params.rate > 0.0 ? params.rate : 0.0', + field: 'host.network.ingress.bytes', + id: 'avg-net-in', + type: 'avg', }, { id: 'calc-invert-rate', @@ -74,20 +70,40 @@ export const hostNetworkTraffic: TSVBMetricModelCreator = ( type: 'calculation', variables: [ { - field: 'posonly-deriv-max-net-in', + field: 'avg-net-in', id: 'var-rate', name: 'rate', }, ], }, { - function: 'sum', - id: 'seriesagg-sum', - type: 'series_agg', + id: 'max-period', + type: 'max', + field: 'metricset.period', + }, + { + id: '3216b170-f192-11ec-a8e3-dd984b7213e2', + type: 'calculation', + variables: [ + { + id: '34e64c30-f192-11ec-a8e3-dd984b7213e2', + name: 'value', + field: 'calc-invert-rate', + }, + { + id: '3886cb80-f192-11ec-a8e3-dd984b7213e2', + name: 'period', + field: 'max-period', + }, + ], + script: 'params.value / (params.period / 1000)', }, ], - split_mode: 'terms', - terms_field: 'system.network.name', + filter: { + language: 'kuery', + query: 'host.network.ingress.bytes : * ', + }, + split_mode: 'everything', }, ], }); diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts index eef480e12aab0..69ebd0aa35947 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts @@ -54,61 +54,77 @@ export const hostSystemOverview: TSVBMetricModelCreator = ( }, { id: 'rx', - split_mode: 'terms', - terms_field: 'system.network.name', metrics: [ { - field: 'system.network.in.bytes', - id: 'max-net-in', - type: 'max', + field: 'host.network.ingress.bytes', + id: 'avg-net-in', + type: 'avg', }, { - field: 'max-net-in', - id: 'deriv-max-net-in', - type: 'derivative', - unit: '1s', + id: 'max-period', + type: 'max', + field: 'metricset.period', }, { - id: 'posonly-deriv-max-net-in', + id: '3216b170-f192-11ec-a8e3-dd984b7213e2', type: 'calculation', - variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-net-in' }], - script: 'params.rate > 0.0 ? params.rate : 0.0', - }, - { - function: 'sum', - id: 'seriesagg-sum', - type: 'series_agg', + variables: [ + { + id: '34e64c30-f192-11ec-a8e3-dd984b7213e2', + name: 'value', + field: 'avg-net-in', + }, + { + id: '3886cb80-f192-11ec-a8e3-dd984b7213e2', + name: 'period', + field: 'max-period', + }, + ], + script: 'params.value / (params.period / 1000)', }, ], + filter: { + language: 'kuery', + query: 'host.network.ingress.bytes : * ', + }, + split_mode: 'everything', }, { id: 'tx', - split_mode: 'terms', - terms_field: 'system.network.name', metrics: [ { - field: 'system.network.out.bytes', - id: 'max-net-out', - type: 'max', + field: 'host.network.egress.bytes', + id: 'avg-net-out', + type: 'avg', }, { - field: 'max-net-out', - id: 'deriv-max-net-out', - type: 'derivative', - unit: '1s', + id: 'max-period', + type: 'max', + field: 'metricset.period', }, { - id: 'posonly-deriv-max-net-out', + id: '3216b170-f192-11ec-a8e3-dd984b7213e2', type: 'calculation', - variables: [{ id: 'var-rate', name: 'rate', field: 'deriv-max-net-out' }], - script: 'params.rate > 0.0 ? params.rate : 0.0', - }, - { - function: 'sum', - id: 'seriesagg-sum', - type: 'series_agg', + variables: [ + { + id: '34e64c30-f192-11ec-a8e3-dd984b7213e2', + name: 'value', + field: 'avg-net-out', + }, + { + id: '3886cb80-f192-11ec-a8e3-dd984b7213e2', + name: 'period', + field: 'max-period', + }, + ], + script: 'params.value / (params.period / 1000)', }, ], + filter: { + language: 'kuery', + query: 'host.network.egress.bytes : * ', + }, + split_mode: 'everything', }, ], }); diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts index 9384f2fb793c1..414412841d387 100644 --- a/x-pack/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/plugins/infra/common/inventory_models/types.ts @@ -64,6 +64,9 @@ export const InventoryMetricRT = rt.keyof({ containerDiskIOBytes: null, containerMemory: null, containerNetworkTraffic: null, + containerK8sOverview: null, + containerK8sCpuUsage: null, + containerK8sMemoryUsage: null, nginxHits: null, nginxRequestRate: null, nginxActiveConnections: null, @@ -288,6 +291,21 @@ export const ESTopMetricsAggRT = rt.type({ }), }); +export const ESMaxPeriodFilterExistsAggRT = rt.type({ + filter: rt.type({ + exists: rt.type({ + field: rt.string, + }), + }), + aggs: rt.type({ + period: rt.type({ + max: rt.type({ + field: rt.string, + }), + }), + }), +}); + export interface SnapshotTermsWithAggregation { terms: { field: string }; aggregations: MetricsUIAggregation; @@ -312,6 +330,7 @@ export const ESAggregationRT = rt.union([ ESTermsWithAggregationRT, ESCaridnalityAggRT, ESTopMetricsAggRT, + ESMaxPeriodFilterExistsAggRT, ]); export const MetricsUIAggregationRT = rt.record(rt.string, ESAggregationRT); diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metrics.test.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metrics.test.ts index 02d2992b3be4b..eb59cdf28af1b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metrics.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metrics.test.ts @@ -27,7 +27,7 @@ describe('createFormatterForMetric()', () => { it('should just work for rates', () => { const metric: MetricsExplorerMetric = { aggregation: 'rate', - field: 'system.network.out.bytes', + field: 'host.network.egress.bytes', }; const format = createFormatterForMetric(metric); expect(format(103929292)).toBe('831.4Mbit/s'); @@ -35,7 +35,7 @@ describe('createFormatterForMetric()', () => { it('should just work for bytes', () => { const metric: MetricsExplorerMetric = { aggregation: 'avg', - field: 'system.network.out.bytes', + field: 'host.network.egress.bytes', }; const format = createFormatterForMetric(metric); expect(format(103929292)).toBe('103.9MB'); diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts index 472e86200cba3..d3dfad0e4bbb0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.test.ts @@ -42,14 +42,14 @@ describe('createTSVBLink()', () => { it('should work with rates', () => { const customOptions: MetricsExplorerOptions = { ...options, - metrics: [{ aggregation: 'rate', field: 'system.network.out.bytes' }], + metrics: [{ aggregation: 'rate', field: 'host.network.egress.bytes' }], }; const link = createTSVBLink(source, customOptions, series, timeRange, chartOptions); expect(link).toStrictEqual({ app: 'visualize', hash: '/create', search: { - _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:bytes,id:test-id,label:'rate(system.network.out.bytes)',line_width:2,metrics:!((field:system.network.out.bytes,id:test-id,type:max),(field:test-id,id:test-id,type:derivative,unit:'1s'),(field:test-id,id:test-id,type:positive_only)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}}/s)),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", + _a: "(filters:!(),linked:!f,query:(language:kuery,query:''),uiState:(),vis:(aggs:!(),params:(axis_formatter:number,axis_min:0,axis_position:left,axis_scale:normal,default_index_pattern:'metricbeat-*',filter:(language:kuery,query:'host.name : \"example-01\"'),id:test-id,index_pattern:'metricbeat-*',interval:auto,series:!((axis_position:right,chart_type:line,color:#6092C0,fill:0,formatter:bytes,id:test-id,label:'rate(host.network.egress.bytes)',line_width:2,metrics:!((field:host.network.egress.bytes,id:test-id,type:max),(field:test-id,id:test-id,type:derivative,unit:'1s'),(field:test-id,id:test-id,type:positive_only)),point_size:0,separate_axis:0,split_mode:everything,stacked:none,value_template:{{value}}/s)),show_grid:1,show_legend:1,time_field:'@timestamp',type:timeseries),title:example-01,type:metrics))", _g: '(refreshInterval:(pause:!t,value:0),time:(from:now-1h,to:now))', type: 'metrics', }, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/metric_to_format.test.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/metric_to_format.test.ts index 567dcfff80e5c..be6a8d240e11e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/metric_to_format.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/metric_to_format.test.ts @@ -16,14 +16,14 @@ describe('metricToFormat()', () => { it('should just work for byte metrics', () => { const metric: MetricsExplorerMetric = { aggregation: 'avg', - field: 'system.network.out.bytes', + field: 'host.network.egress.bytes', }; expect(metricToFormat(metric)).toBe(InfraFormatterType.bytes); }); it('should just work for rate bytes metrics', () => { const metric: MetricsExplorerMetric = { aggregation: 'rate', - field: 'system.network.out.bytes', + field: 'host.network.egress.bytes', }; expect(metricToFormat(metric)).toBe(InfraFormatterType.bits); }); diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/__snapshots__/create_metrics_aggregations.test.ts.snap b/x-pack/plugins/infra/server/lib/metrics/lib/__snapshots__/create_metrics_aggregations.test.ts.snap index bbfe7e9cf0f9f..36e456c507f24 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/__snapshots__/create_metrics_aggregations.test.ts.snap +++ b/x-pack/plugins/infra/server/lib/metrics/lib/__snapshots__/create_metrics_aggregations.test.ts.snap @@ -16,7 +16,7 @@ Object { }, "metric_1_max": Object { "max": Object { - "field": "system.network.in.bytes", + "field": "test.field.that.is.a.counter", }, }, } diff --git a/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts b/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts index 2e2d1736e5925..00b895ec19241 100644 --- a/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts +++ b/x-pack/plugins/infra/server/lib/metrics/lib/create_metrics_aggregations.test.ts @@ -22,7 +22,7 @@ const options: MetricsAPIRequest = { { id: 'metric_1', aggregations: { - metric_1_max: { max: { field: 'system.network.in.bytes' } }, + metric_1_max: { max: { field: 'test.field.that.is.a.counter' } }, metric_1: { derivative: { buckets_path: 'metric_1_max', gap_policy: 'skip', unit: '1s' } }, }, }, diff --git a/x-pack/plugins/infra/server/lib/metrics/types.ts b/x-pack/plugins/infra/server/lib/metrics/types.ts index 82e174f4f94c1..cb2fefaf9b7e3 100644 --- a/x-pack/plugins/infra/server/lib/metrics/types.ts +++ b/x-pack/plugins/infra/server/lib/metrics/types.ts @@ -35,12 +35,18 @@ export const TopMetricsTypeRT = rt.type({ ), }); +export const MaxPeriodFilterExistsTypeRT = rt.type({ + doc_count: rt.number, + period: BasicMetricValueRT, +}); + export const MetricValueTypeRT = rt.union([ BasicMetricValueRT, NormalizedMetricValueRT, PercentilesTypeRT, PercentilesKeyedTypeRT, TopMetricsTypeRT, + MaxPeriodFilterExistsTypeRT, ]); export type MetricValueType = rt.TypeOf<typeof MetricValueTypeRT>; diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.test.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.test.ts index fbf7abc1c0530..6e391aeb45246 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.test.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.test.ts @@ -34,13 +34,13 @@ describe('convertMetricToMetricsAPIMetric(metric, index)', () => { runTestForBasic('cardinality'); runTest( - { aggregation: 'rate', field: 'system.network.in.bytes' }, + { aggregation: 'rate', field: 'test.field.that.is.a.counter' }, { id: 'metric_1', aggregations: { metric_1_max: { max: { - field: 'system.network.in.bytes', + field: 'test.field.that.is.a.counter', }, }, metric_1_deriv: { diff --git a/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts b/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts index c3b4693372db3..cf8227be460af 100644 --- a/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts +++ b/x-pack/plugins/infra/server/routes/overview/lib/convert_es_response_to_top_nodes_response.ts @@ -27,16 +27,16 @@ export const convertESResponseToTopNodesResponse = ( cpu: bucket.cpu.value, iowait: bucket.iowait.value, load: bucket.load.value, - rx: bucket.rx.value, - tx: bucket.tx.value, + rx: bucket.rx?.value || null, + tx: bucket.tx?.value || null, }; }), cpu: node.cpu.value, iowait: node.iowait.value, load: node.load.value, uptime: node.uptime.value, - rx: node.rx.value, - tx: node.tx.value, + rx: node.rx?.value || null, + tx: node.tx?.value || null, }; }), }; diff --git a/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts b/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts index a915a7882e231..20bf2324c29d1 100644 --- a/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts +++ b/x-pack/plugins/infra/server/routes/overview/lib/create_top_nodes_query.ts @@ -75,14 +75,68 @@ export const createTopNodesQuery = ( field: 'system.load.15', }, }, + rx_avg: { + avg: { + field: 'host.network.ingress.bytes', + }, + }, + rx_period: { + filter: { + exists: { + field: 'host.network.ingress.bytes', + }, + }, + aggs: { + period: { + max: { + field: 'metricset.period', + }, + }, + }, + }, rx: { - sum: { - field: 'host.network.in.bytes', + bucket_script: { + buckets_path: { + value: 'rx_avg', + period: 'rx_period>period', + }, + script: { + source: 'params.value / (params.period / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, + tx_avg: { + avg: { + field: 'host.network.egress.bytes', + }, + }, + tx_period: { + filter: { + exists: { + field: 'host.network.egress.bytes', + }, + }, + aggs: { + period: { + max: { + field: 'metricset.period', + }, + }, }, }, tx: { - sum: { - field: 'host.network.out.bytes', + bucket_script: { + buckets_path: { + value: 'tx_avg', + period: 'tx_period>period', + }, + script: { + source: 'params.value / (params.period / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', }, }, timeseries: { @@ -110,16 +164,70 @@ export const createTopNodesQuery = ( field: 'system.load.15', }, }, - rx: { - rate: { + rx_avg: { + avg: { field: 'host.network.ingress.bytes', }, }, - tx: { - rate: { + rx_period: { + filter: { + exists: { + field: 'host.network.ingress.bytes', + }, + }, + aggs: { + period: { + max: { + field: 'metricset.period', + }, + }, + }, + }, + rx: { + bucket_script: { + buckets_path: { + value: 'rx_avg', + period: 'rx_period>period', + }, + script: { + source: 'params.value / (params.period / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, + tx_avg: { + avg: { field: 'host.network.egress.bytes', }, }, + tx_period: { + filter: { + exists: { + field: 'host.network.egress.bytes', + }, + }, + aggs: { + period: { + max: { + field: 'metricset.period', + }, + }, + }, + }, + tx: { + bucket_script: { + buckets_path: { + value: 'tx_avg', + period: 'tx_period>period', + }, + script: { + source: 'params.value / (params.period / 1000)', + lang: 'painless', + }, + gap_policy: 'skip', + }, + }, }, }, }, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 7d96db4027bad..28537e7934555 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -190,6 +190,7 @@ export function TableDimensionEditor( </EuiFormRow> {!column.isTransposed && ( <EuiFormRow + fullWidth label={i18n.translate('xpack.lens.table.columnVisibilityLabel', { defaultMessage: 'Hide column', })} @@ -266,6 +267,7 @@ export function TableDimensionEditor( })} > <EuiFieldText + fullWidth compressed data-test-subj="lnsDatatable_summaryrow_label" value={summaryLabel ?? fallbackSummaryLabel} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 385f92b0dc05d..551e3f85a64fb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -46,7 +46,7 @@ import { trackUiEvent } from '../lens_ui_telemetry'; import { loadIndexPatterns, syncExistingFields } from './loader'; import { fieldExists } from './pure_helpers'; import { Loader } from '../loader'; -import { LensFieldIcon } from './lens_field_icon'; +import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { FieldGroups, FieldList } from './field_list'; export type Props = Omit<DatasourceDataPanelProps<IndexPatternPrivateState>, 'core'> & { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx index 52a6ec68c294a..46ed51f35d9b0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/bucket_nesting_editor.tsx @@ -64,7 +64,7 @@ export function BucketNestingEditor({ defaultMessage: 'Group by this field first', }); return ( - <EuiFormRow label={useAsTopLevelAggCopy} display="columnCompressedSwitch"> + <EuiFormRow label={useAsTopLevelAggCopy} display="columnCompressedSwitch" fullWidth> <EuiSwitch compressed label={useAsTopLevelAggCopy} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx index a79388d456b73..4e667e2aa30d6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx @@ -19,7 +19,7 @@ import { import ReactDOM from 'react-dom'; import type { IndexPatternDimensionEditorProps } from './dimension_panel'; import type { OperationSupportMatrix } from './operation_support'; -import type { GenericIndexPatternColumn } from '../indexpattern'; +import { deleteColumn, GenericIndexPatternColumn } from '../indexpattern'; import { operationDefinitionMap, getOperationDisplay, @@ -31,12 +31,13 @@ import { FieldBasedIndexPatternColumn, canTransition, DEFAULT_TIME_SCALE, + adjustColumnReferencesForChangedColumn, } from '../operations'; import { mergeLayer } from '../state_helpers'; import { hasField } from '../pure_utils'; import { fieldIsInvalid } from '../utils'; import { BucketNestingEditor } from './bucket_nesting_editor'; -import type { IndexPattern, IndexPatternLayer } from '../types'; +import type { IndexPattern, IndexPatternField, IndexPatternLayer } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { FormatSelector } from './format_selector'; import { ReferenceEditor } from './reference_editor'; @@ -60,8 +61,8 @@ import { FieldInput } from './field_input'; import { NameInput } from '../../shared_components'; import { ParamEditorProps } from '../operations/definitions'; import { WrappingHelpPopover } from '../help_popover'; - -const operationPanels = getOperationDisplay(); +import { isColumn } from '../operations/definitions/helpers'; +import { FieldChoiceWithOperationType } from './field_select'; export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { selectedColumn?: GenericIndexPatternColumn; @@ -70,6 +71,8 @@ export interface DimensionEditorProps extends IndexPatternDimensionEditorProps { currentIndexPattern: IndexPattern; } +const operationDisplay = getOperationDisplay(); + export function DimensionEditor(props: DimensionEditorProps) { const { selectedColumn, @@ -114,15 +117,47 @@ export function DimensionEditor(props: DimensionEditorProps) { ); const setStateWrapper = ( - setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer), + setter: + | IndexPatternLayer + | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + | GenericIndexPatternColumn, options: { forceRender?: boolean } = {} ) => { - const hypotheticalLayer = typeof setter === 'function' ? setter(state.layers[layerId]) : setter; - const isDimensionComplete = Boolean(hypotheticalLayer.columns[columnId]); + const layer = state.layers[layerId]; + let hypotethicalLayer: IndexPatternLayer; + if (isColumn(setter)) { + hypotethicalLayer = { + ...layer, + columns: { + ...layer.columns, + [columnId]: setter, + }, + }; + } else { + hypotethicalLayer = typeof setter === 'function' ? setter(state.layers[layerId]) : setter; + } + const isDimensionComplete = Boolean(hypotethicalLayer.columns[columnId]); + setState( (prevState) => { - const layer = typeof setter === 'function' ? setter(prevState.layers[layerId]) : setter; - return mergeLayer({ state: prevState, layerId, newLayer: layer }); + let outputLayer: IndexPatternLayer; + const prevLayer = prevState.layers[layerId]; + if (isColumn(setter)) { + outputLayer = { + ...prevLayer, + columns: { + ...prevLayer.columns, + [columnId]: setter, + }, + }; + } else { + outputLayer = typeof setter === 'function' ? setter(prevState.layers[layerId]) : setter; + } + return mergeLayer({ + state: prevState, + layerId, + newLayer: adjustColumnReferencesForChangedColumn(outputLayer, columnId), + }); }, { isDimensionComplete, @@ -189,7 +224,10 @@ export function DimensionEditor(props: DimensionEditorProps) { // Note: it forced a rerender at this point to avoid UI glitches in async updates (another hack upstream) // TODO: revisit this once we get rid of updateDatasourceAsync upstream const moveDefinetelyToStaticValueAndUpdate = ( - setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + setter: + | IndexPatternLayer + | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + | GenericIndexPatternColumn ) => { if (temporaryStaticValue) { setTemporaryState('none'); @@ -206,6 +244,9 @@ export function DimensionEditor(props: DimensionEditorProps) { } ); } + if (isColumn(setter)) { + throw new Error('static value should only be updated by the whole layer'); + } }; const ParamEditor = getParamEditor( @@ -290,23 +331,23 @@ export function DimensionEditor(props: DimensionEditorProps) { color = 'subdued'; } - let label: EuiListGroupItemProps['label'] = operationPanels[operationType].displayName; + let label: EuiListGroupItemProps['label'] = operationDisplay[operationType].displayName; if (isActive && disabledStatus) { label = ( <EuiToolTip content={disabledStatus} display="block" position="left"> <EuiText color="danger" size="s"> - <strong>{operationPanels[operationType].displayName}</strong> + <strong>{operationDisplay[operationType].displayName}</strong> </EuiText> </EuiToolTip> ); } else if (disabledStatus) { label = ( <EuiToolTip content={disabledStatus} display="block" position="left"> - <span>{operationPanels[operationType].displayName}</span> + <span>{operationDisplay[operationType].displayName}</span> </EuiToolTip> ); } else if (isActive) { - label = <strong>{operationPanels[operationType].displayName}</strong>; + label = <strong>{operationDisplay[operationType].displayName}</strong>; } return { @@ -438,6 +479,7 @@ export function DimensionEditor(props: DimensionEditorProps) { if (temporaryQuickFunction) { setTemporaryState('none'); } + const newLayer = replaceColumn({ layer: props.state.layers[props.layerId], indexPattern: currentIndexPattern, @@ -475,11 +517,16 @@ export function DimensionEditor(props: DimensionEditorProps) { const FieldInputComponent = selectedOperationDefinition?.renderFieldInput || FieldInput; - const paramEditorProps: ParamEditorProps<GenericIndexPatternColumn> = { + const paramEditorProps: ParamEditorProps< + GenericIndexPatternColumn, + | IndexPatternLayer + | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + | GenericIndexPatternColumn + > = { layer: state.layers[layerId], layerId, activeData: props.activeData, - updateLayer: (setter) => { + paramEditorUpdater: (setter) => { if (temporaryQuickFunction) { setTemporaryState('none'); } @@ -494,6 +541,8 @@ export function DimensionEditor(props: DimensionEditorProps) { isFullscreen, setIsCloseable, paramEditorCustomProps, + ReferenceEditor, + existingFields: state.existingFields, ...services, }; @@ -523,21 +572,75 @@ export function DimensionEditor(props: DimensionEditorProps) { <> {selectedColumn.references.map((referenceId, index) => { const validation = selectedOperationDefinition.requiredReferences[index]; - + const layer = state.layers[layerId]; return ( <ReferenceEditor + operationDefinitionMap={operationDefinitionMap} key={index} - layer={state.layers[layerId]} + layer={layer} layerId={layerId} activeData={props.activeData} columnId={referenceId} - updateLayer={( + column={layer.columns[referenceId]} + incompleteColumn={ + layer.incompleteColumns ? layer.incompleteColumns[referenceId] : undefined + } + onDeleteColumn={() => { + updateLayer( + deleteColumn({ + layer, + columnId: referenceId, + indexPattern: currentIndexPattern, + }) + ); + }} + onChooseFunction={(operationType: string, field?: IndexPatternField) => { + updateLayer( + insertOrReplaceColumn({ + layer, + columnId: referenceId, + op: operationType, + indexPattern: currentIndexPattern, + field, + visualizationGroups: dimensionGroups, + }) + ); + }} + onChooseField={(choice: FieldChoiceWithOperationType) => { + trackUiEvent('indexpattern_dimension_field_changed'); + updateLayer( + insertOrReplaceColumn({ + layer, + columnId: referenceId, + indexPattern: currentIndexPattern, + op: choice.operationType, + field: currentIndexPattern.getFieldByName(choice.field), + visualizationGroups: dimensionGroups, + }) + ); + }} + paramEditorUpdater={( setter: | IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + | GenericIndexPatternColumn ) => { - updateLayer( - typeof setter === 'function' ? setter(state.layers[layerId]) : setter + let newLayer: IndexPatternLayer; + if (typeof setter === 'function') { + newLayer = setter(layer); + } else if (isColumn(setter)) { + newLayer = { + ...layer, + columns: { + ...layer.columns, + [referenceId]: setter, + }, + }; + } else { + newLayer = setter; + } + return updateLayer( + adjustColumnReferencesForChangedColumn(newLayer, referenceId) ); }} validation={validation} @@ -548,9 +651,8 @@ export function DimensionEditor(props: DimensionEditorProps) { labelAppend={selectedOperationDefinition?.getHelpMessage?.({ data: props.data, uiSettings: props.uiSettings, - currentColumn: state.layers[layerId].columns[columnId], + currentColumn: layer.columns[columnId], })} - dimensionGroups={dimensionGroups} isFullscreen={isFullscreen} toggleFullscreen={toggleFullscreen} setIsCloseable={setIsCloseable} @@ -600,19 +702,23 @@ export function DimensionEditor(props: DimensionEditorProps) { const customParamEditor = ParamEditor ? ( <> <ParamEditor + existingFields={state.existingFields} layer={state.layers[layerId]} - layerId={layerId} activeData={props.activeData} - updateLayer={temporaryStaticValue ? moveDefinetelyToStaticValueAndUpdate : setStateWrapper} + paramEditorUpdater={ + temporaryStaticValue ? moveDefinetelyToStaticValueAndUpdate : setStateWrapper + } columnId={columnId} currentColumn={state.layers[layerId].columns[columnId]} + operationDefinitionMap={operationDefinitionMap} + layerId={layerId} + paramEditorCustomProps={paramEditorCustomProps} dateRange={dateRange} + isFullscreen={isFullscreen} indexPattern={currentIndexPattern} - operationDefinitionMap={operationDefinitionMap} toggleFullscreen={toggleFullscreen} - isFullscreen={isFullscreen} setIsCloseable={setIsCloseable} - paramEditorCustomProps={paramEditorCustomProps} + ReferenceEditor={ReferenceEditor} {...services} /> </> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 7fc76300a73ec..9606cbbf21592 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -49,11 +49,16 @@ import { DimensionEditor } from './dimension_editor'; import { AdvancedOptions } from './advanced_options'; import { layerTypes } from '../../../common'; +jest.mock('./reference_editor', () => ({ + ReferenceEditor: () => null, +})); jest.mock('../loader'); jest.mock('../query_input', () => ({ QueryInput: () => null, })); + jest.mock('../operations'); + jest.mock('lodash', () => { const original = jest.requireActual('lodash'); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts index 744033a2428fa..7546ff86b8b6e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts @@ -36,7 +36,7 @@ interface GetDropPropsArgs { type DropProps = { dropTypes: DropType[]; nextLabel?: string } | undefined; -const operationLabels = getOperationDisplay(); +const operationDisplay = getOperationDisplay(); export function getNewOperation( field: IndexPatternField | undefined | false, @@ -133,7 +133,7 @@ function getDropPropsForField({ const newOperation = getNewOperation(source.field, target.filterOperations, targetColumn); if (isTheSameIndexPattern && newOperation) { - const nextLabel = operationLabels[newOperation].displayName; + const nextLabel = operationDisplay[newOperation].displayName; if (!targetColumn) { return { dropTypes: ['field_add'], nextLabel }; @@ -227,7 +227,7 @@ function getDropPropsFromIncompatibleGroup( return { dropTypes, - nextLabel: operationLabels[newOperationForSource].displayName, + nextLabel: operationDisplay[newOperationForSource].displayName, }; } } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index 6bbe335d66307..16e70f5657db0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -7,30 +7,19 @@ import './field_select.scss'; import { partition } from 'lodash'; -import React, { useMemo, useRef } from 'react'; +import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { - EuiComboBox, - EuiFlexGroup, - EuiFlexItem, - EuiComboBoxOptionOption, - EuiComboBoxProps, -} from '@elastic/eui'; -import classNames from 'classnames'; -import { LensFieldIcon } from '../lens_field_icon'; +import { EuiComboBoxOptionOption, EuiComboBoxProps } from '@elastic/eui'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { fieldExists } from '../pure_helpers'; -import { TruncatedLabel } from './truncated_label'; import type { OperationType } from '../indexpattern'; -import type { DataType } from '../../types'; import type { OperationSupportMatrix } from './operation_support'; import type { IndexPattern, IndexPatternPrivateState } from '../types'; -export interface FieldChoice { - type: 'field'; - field: string; +import { FieldOption, FieldOptionValue, FieldPicker } from '../../shared_components/field_picker'; + +export type FieldChoiceWithOperationType = FieldOptionValue & { operationType: OperationType; -} +}; export interface FieldSelectProps extends EuiComboBoxProps<EuiComboBoxOptionOption['value']> { currentIndexPattern: IndexPattern; @@ -38,7 +27,7 @@ export interface FieldSelectProps extends EuiComboBoxProps<EuiComboBoxOptionOpti selectedField?: string; incompleteOperation?: OperationType; operationByField: OperationSupportMatrix['operationByField']; - onChoose: (choice: FieldChoice) => void; + onChoose: (choice: FieldChoiceWithOperationType) => void; onDeleteColumn?: () => void; existingFields: IndexPatternPrivateState['existingFields']; fieldIsInvalid: boolean; @@ -46,10 +35,6 @@ export interface FieldSelectProps extends EuiComboBoxProps<EuiComboBoxOptionOpti 'data-test-subj'?: string; } -const DEFAULT_COMBOBOX_WIDTH = 305; -const COMBOBOX_PADDINGS = 90; -const DEFAULT_FONT = '14px Inter'; - export function FieldSelect({ currentIndexPattern, incompleteOperation, @@ -77,7 +62,6 @@ export function FieldSelect({ fields, (field) => currentIndexPattern.getFieldByName(field)?.type === 'document' ); - const containsData = (field: string) => currentIndexPattern.getFieldByName(field)?.type === 'document' || fieldExists(existingFields, currentIndexPattern.title, field); @@ -105,10 +89,6 @@ export function FieldSelect({ }, exists, compatible, - className: classNames({ - 'lnFieldSelect__option--incompatible': !compatible, - 'lnFieldSelect__option--nonExistant': !exists, - }), 'data-test-subj': `lns-fieldOption${compatible ? '' : 'Incompatible'}-${field}`, }; }) @@ -162,91 +142,33 @@ export function FieldSelect({ existingFields, markAllFieldsCompatible, ]); - const comboBoxRef = useRef<HTMLInputElement>(null); - const [labelProps, setLabelProps] = React.useState<{ - width: number; - font: string; - }>({ - width: DEFAULT_COMBOBOX_WIDTH - COMBOBOX_PADDINGS, - font: DEFAULT_FONT, - }); - - const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => { - if (comboBoxRef.current) { - const current = { - ...labelProps, - width: comboBoxRef.current?.clientWidth - COMBOBOX_PADDINGS, - }; - if (shouldRecomputeAll) { - current.font = window.getComputedStyle(comboBoxRef.current).font; - } - setLabelProps(current); - } - }; - - useEffectOnce(() => { - if (comboBoxRef.current) { - computeStyles(undefined, true); - } - window.addEventListener('resize', computeStyles); - }); return ( - <div ref={comboBoxRef}> - <EuiComboBox - fullWidth - compressed - isClearable={false} - data-test-subj={dataTestSub ?? 'indexPattern-dimension-field'} - placeholder={i18n.translate('xpack.lens.indexPattern.fieldPlaceholder', { - defaultMessage: 'Field', - })} - options={memoizedFieldOptions as unknown as EuiComboBoxOptionOption[]} - isInvalid={Boolean(incompleteOperation || fieldIsInvalid)} - selectedOptions={ - (selectedOperationType && selectedField - ? [ - { - label: fieldIsInvalid - ? selectedField - : currentIndexPattern.getFieldByName(selectedField)?.displayName ?? - selectedField, - value: { type: 'field', field: selectedField }, - }, - ] - : []) as unknown as EuiComboBoxOptionOption[] + <FieldPicker<FieldChoiceWithOperationType> + selectedOptions={ + (selectedOperationType && selectedField + ? [ + { + label: + (selectedOperationType && + selectedField && + currentIndexPattern.getFieldByName(selectedField)?.displayName) ?? + selectedField, + value: { type: 'field', field: selectedField }, + }, + ] + : []) as unknown as Array<FieldOption<FieldChoiceWithOperationType>> + } + options={memoizedFieldOptions as Array<FieldOption<FieldChoiceWithOperationType>>} + onChoose={(choice) => { + if (choice && choice.field !== selectedField) { + trackUiEvent('indexpattern_dimension_field_changed'); + onChoose(choice); } - singleSelection={{ asPlainText: true }} - onChange={(choices) => { - if (choices.length === 0) { - onDeleteColumn?.(); - return; - } - - const choice = choices[0].value as unknown as FieldChoice; - - if (choice.field !== selectedField) { - trackUiEvent('indexpattern_dimension_field_changed'); - onChoose(choice); - } - }} - renderOption={(option, searchValue) => { - return ( - <EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}> - <EuiFlexItem grow={null}> - <LensFieldIcon - type={(option.value as unknown as { dataType: DataType }).dataType} - fill="none" - /> - </EuiFlexItem> - <EuiFlexItem> - <TruncatedLabel {...labelProps} label={option.label} search={searchValue} /> - </EuiFlexItem> - </EuiFlexGroup> - ); - }} - {...rest} - /> - </div> + }} + onDelete={onDeleteColumn} + fieldIsInvalid={Boolean(incompleteOperation || fieldIsInvalid)} + data-test-subj={dataTestSub ?? 'indexPattern-dimension-field'} + /> ); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx index 857d0cfb9c1d2..6304f1ff64f91 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx @@ -21,25 +21,41 @@ import { ReferenceEditor, ReferenceEditorProps } from './reference_editor'; import { insertOrReplaceColumn, LastValueIndexPatternColumn, + operationDefinitionMap, TermsIndexPatternColumn, } from '../operations'; import { FieldSelect } from './field_select'; +import { IndexPatternLayer } from '../types'; jest.mock('../operations'); describe('reference editor', () => { let wrapper: ReactWrapper | ShallowWrapper; - let updateLayer: jest.Mock<ReferenceEditorProps['updateLayer']>; - + let paramEditorUpdater: jest.Mock<ReferenceEditorProps['paramEditorUpdater']>; + + const layer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Top values of dest', + dataType: 'string', + isBucketed: true, + operationType: 'terms', + sourceField: 'dest', + params: { size: 5, orderBy: { type: 'alphabetical' }, orderDirection: 'desc' }, + } as TermsIndexPatternColumn, + }, + }; function getDefaultArgs() { return { - layer: { - indexPatternId: '1', - columns: {}, - columnOrder: [], - }, + layer, + column: layer.columns.ref, + onChooseField: jest.fn(), + onChooseFunction: jest.fn(), + onDeleteColumn: jest.fn(), columnId: 'ref', - updateLayer, + paramEditorUpdater, selectionStyle: 'full' as const, currentIndexPattern: createMockedIndexPattern(), existingFields: { @@ -63,11 +79,12 @@ describe('reference editor', () => { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + operationDefinitionMap, }; } beforeEach(() => { - updateLayer = jest.fn().mockImplementation((newLayer) => { + paramEditorUpdater = jest.fn().mockImplementation((newLayer) => { if (wrapper instanceof ReactWrapper) { wrapper.setProps({ layer: newLayer }); } @@ -90,6 +107,7 @@ describe('reference editor', () => { input: ['field'], validateMetadata: (meta: OperationMetadata) => meta.dataType === 'number', }} + column={undefined} /> ); @@ -115,27 +133,67 @@ describe('reference editor', () => { ); }); - it('should indicate functions and fields that are incompatible with the current', () => { + it('should indicate fields that are incompatible with the current', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Top values of dest', - dataType: 'string', - isBucketed: true, - operationType: 'terms', - sourceField: 'dest', - params: { size: 5, orderBy: { type: 'alphabetical' }, orderDirection: 'desc' }, - } as TermsIndexPatternColumn, - }, + layer={newLayer} + column={newLayer.columns.ref} + validation={{ + input: ['field'], + validateMetadata: (meta: OperationMetadata) => !meta.isBucketed, }} + /> + ); + + const fields = wrapper + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-dimension-field"]') + .prop('options'); + + const findFieldDataTestSubj = (l: string) => { + return fields![0].options!.find(({ label }) => label === l)!['data-test-subj']; + }; + expect(findFieldDataTestSubj('timestampLabel')).toContain('Incompatible'); + expect(findFieldDataTestSubj('source')).toContain('Incompatible'); + expect(findFieldDataTestSubj('memory')).toContain('lns-fieldOption-memory'); + }); + + it('should indicate functions that are incompatible with the current', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Unique count of dest', + dataType: 'string', + isBucketed: false, + operationType: 'unique_count', + sourceField: 'dest', + }, + }, + } as IndexPatternLayer; + wrapper = mount( + <ReferenceEditor + {...getDefaultArgs()} + layer={newLayer} + column={newLayer.columns.ref} validation={{ input: ['field'], - validateMetadata: (meta: OperationMetadata) => meta.isBucketed, + validateMetadata: (meta: OperationMetadata) => !meta.isBucketed, }} /> ); @@ -144,36 +202,31 @@ describe('reference editor', () => { .find(EuiComboBox) .filter('[data-test-subj="indexPattern-reference-function"]') .prop('options'); - expect(functions.find(({ label }) => label === 'Date histogram')!['data-test-subj']).toContain( + + expect(functions.find(({ label }) => label === 'Average')!['data-test-subj']).toContain( 'incompatible' ); - - const fields = wrapper - .find(EuiComboBox) - .filter('[data-test-subj="indexPattern-dimension-field"]') - .prop('options'); - expect( - fields![0].options!.find(({ label }) => label === 'timestampLabel')!['data-test-subj'] - ).toContain('Incompatible'); }); it('should not update when selecting the same operation', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'bytes', - }, - }, - }} + layer={newLayer} + column={newLayer.columns.ref} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => meta.dataType === 'number', @@ -193,26 +246,30 @@ describe('reference editor', () => { }); it('should keep the field when replacing an existing reference with a compatible function', () => { + const onChooseFunction = jest.fn(); + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'bytes', - }, - }, - }} + layer={newLayer} + column={newLayer.columns.ref} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => meta.dataType === 'number', }} + onChooseFunction={onChooseFunction} /> ); @@ -225,31 +282,35 @@ describe('reference editor', () => { comboBox.prop('onChange')!([option]); }); - expect(insertOrReplaceColumn).toHaveBeenCalledWith( + expect(onChooseFunction).toHaveBeenCalledWith( + 'max', expect.objectContaining({ - op: 'max', - field: expect.objectContaining({ name: 'bytes' }), + name: 'bytes', }) ); }); it('should transition to another function with incompatible field', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Unique count of dest', + dataType: 'string', + isBucketed: false, + operationType: 'unique_count', + sourceField: 'dest', + }, + }, + } as IndexPatternLayer; + const onChooseFunction = jest.fn(); wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'bytes', - }, - }, - }} + onChooseFunction={onChooseFunction} + column={newLayer.columns.ref} + layer={newLayer} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, @@ -260,39 +321,36 @@ describe('reference editor', () => { const comboBox = wrapper .find(EuiComboBox) .filter('[data-test-subj="indexPattern-reference-function"]'); - const option = comboBox.prop('options')!.find(({ label }) => label === 'Date histogram')!; + const option = comboBox.prop('options')!.find(({ label }) => label === 'Average')!; act(() => { comboBox.prop('onChange')!([option]); }); - expect(insertOrReplaceColumn).toHaveBeenCalledWith( - expect.objectContaining({ - op: 'date_histogram', - field: undefined, - }) - ); + expect(onChooseFunction).toHaveBeenCalledWith('average', undefined); }); it("should show the sub-function as invalid if there's no field compatible with it", () => { // This may happen for saved objects after changing the type of a field + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} currentIndexPattern={createMockedIndexPatternWithoutType('number')} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'bytes', - }, - }, - }} + column={newLayer.columns.ref} + layer={newLayer} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, @@ -321,6 +379,8 @@ describe('reference editor', () => { wrapper = mount( <ReferenceEditor {...getDefaultArgs()} + column={undefined} + currentIndexPattern={createMockedIndexPatternWithoutType('number')} validation={{ input: ['field', 'fullReference', 'managedReference'], validateMetadata: (meta: OperationMetadata) => true, @@ -331,8 +391,8 @@ describe('reference editor', () => { const subFunctionSelect = wrapper .find('[data-test-subj="indexPattern-reference-function"]') .first(); - expect(subFunctionSelect.prop('isInvalid')).toEqual(true); + expect(subFunctionSelect.prop('selectedOptions')).not.toEqual( expect.arrayContaining([expect.objectContaining({ value: 'math' })]) ); @@ -345,6 +405,7 @@ describe('reference editor', () => { wrapper = mount( <ReferenceEditor {...getDefaultArgs()} + column={undefined} selectionStyle={'field' as const} validation={{ input: ['field'], @@ -360,25 +421,28 @@ describe('reference editor', () => { }); it('should pass the incomplete operation info to FieldSelect', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + }, + incompleteColumns: { + ref: { operationType: 'max' }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'bytes', - }, - }, - incompleteColumns: { - ref: { operationType: 'max' }, - }, - }} + incompleteColumn={newLayer.incompleteColumns?.ref} + column={newLayer.columns.ref} + layer={newLayer} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, @@ -395,25 +459,28 @@ describe('reference editor', () => { }); it('should pass the incomplete field info to FieldSelect', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + }, + }, + incompleteColumns: { + ref: { sourceField: 'timestamp' }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'bytes', - }, - }, - incompleteColumns: { - ref: { sourceField: 'timestamp' }, - }, - }} + layer={newLayer} + incompleteColumn={newLayer.incompleteColumns?.ref} + column={newLayer.columns.ref} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, @@ -432,6 +499,7 @@ describe('reference editor', () => { wrapper = mount( <ReferenceEditor {...getDefaultArgs()} + column={undefined} selectionStyle="field" validation={{ input: ['field'], @@ -449,22 +517,24 @@ describe('reference editor', () => { }); it('should show the FieldSelect as invalid if the selected field is missing', () => { + const newLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Average of missing', + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'missing', + }, + }, + } as IndexPatternLayer; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Average of missing', - dataType: 'number', - isBucketed: false, - operationType: 'average', - sourceField: 'missing', - }, - }, - }} + layer={newLayer} + column={newLayer.columns.ref} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, @@ -481,25 +551,27 @@ describe('reference editor', () => { }); it('should show the ParamEditor for functions that offer one', () => { + const lastValueLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Last value of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'last_value', + sourceField: 'bytes', + params: { + sortField: 'timestamp', + }, + } as LastValueIndexPatternColumn, + }, + }; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Last value of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'last_value', - sourceField: 'bytes', - params: { - sortField: 'timestamp', - }, - } as LastValueIndexPatternColumn, - }, - }} + column={lastValueLayer.columns.ref} + layer={lastValueLayer} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, @@ -513,28 +585,31 @@ describe('reference editor', () => { }); it('should hide the ParamEditor for incomplete functions', () => { + const lastValueLayer = { + indexPatternId: '1', + columnOrder: ['ref'], + columns: { + ref: { + label: 'Last value of bytes', + dataType: 'number', + isBucketed: false, + operationType: 'last_value', + sourceField: 'bytes', + params: { + sortField: 'timestamp', + }, + } as LastValueIndexPatternColumn, + }, + incompleteColumns: { + ref: { operationType: 'max' }, + }, + }; wrapper = mount( <ReferenceEditor {...getDefaultArgs()} - layer={{ - indexPatternId: '1', - columnOrder: ['ref'], - columns: { - ref: { - label: 'Last value of bytes', - dataType: 'number', - isBucketed: false, - operationType: 'last_value', - sourceField: 'bytes', - params: { - sortField: 'timestamp', - }, - } as LastValueIndexPatternColumn, - }, - incompleteColumns: { - ref: { operationType: 'max' }, - }, - }} + incompleteColumn={lastValueLayer.incompleteColumns.ref} + column={lastValueLayer.columns.ref} + layer={lastValueLayer} validation={{ input: ['field'], validateMetadata: (meta: OperationMetadata) => true, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx index 4082580cb456a..57782fa157ca7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.tsx @@ -8,13 +8,7 @@ import './dimension_editor.scss'; import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiFormRow, - EuiFormRowProps, - EuiSpacer, - EuiComboBox, - EuiComboBoxOptionOption, -} from '@elastic/eui'; +import { EuiFormRowProps, EuiSpacer, EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from '@kbn/core/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; @@ -22,24 +16,58 @@ import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DateRange } from '../../../common'; import type { OperationSupportMatrix } from './operation_support'; -import type { OperationType } from '../indexpattern'; +import type { GenericIndexPatternColumn, OperationType } from '../indexpattern'; import { - operationDefinitionMap, getOperationDisplay, - insertOrReplaceColumn, - deleteColumn, isOperationAllowedAsReference, FieldBasedIndexPatternColumn, RequiredReference, + IncompleteColumn, + GenericOperationDefinition, } from '../operations'; -import { FieldSelect } from './field_select'; +import { FieldChoiceWithOperationType, FieldSelect } from './field_select'; import { hasField } from '../pure_utils'; -import type { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from '../types'; +import type { + IndexPattern, + IndexPatternField, + IndexPatternLayer, + IndexPatternPrivateState, +} from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; -import type { ParamEditorCustomProps, VisualizationDimensionGroupConfig } from '../../types'; +import type { ParamEditorCustomProps } from '../../types'; import type { IndexPatternDimensionEditorProps } from './dimension_panel'; +import { FormRow } from '../operations/definitions/shared_components'; + +const operationDisplay = getOperationDisplay(); + +const getFunctionOptions = ( + operationSupportMatrix: OperationSupportMatrix & { + operationTypes: Set<OperationType>; + }, + operationDefinitionMap: Record<string, GenericOperationDefinition>, + column?: GenericIndexPatternColumn +): Array<EuiComboBoxOptionOption<OperationType>> => { + return Array.from(operationSupportMatrix.operationTypes).map((operationType) => { + const def = operationDefinitionMap[operationType]; + const label = operationDisplay[operationType].displayName; + const isCompatible = + !column || + (column && + hasField(column) && + def.input === 'field' && + operationSupportMatrix.fieldByOperation[operationType]?.has(column.sourceField)) || + (column && !hasField(column) && def.input !== 'field'); -const operationPanels = getOperationDisplay(); + return { + label, + value: operationType, + className: 'lnsIndexPatternDimensionEditor__operation', + 'data-test-subj': `lns-indexPatternDimension-${operationType}${ + isCompatible ? '' : ' incompatible' + }`, + }; + }); +}; export interface ReferenceEditorProps { layer: IndexPatternLayer; @@ -48,18 +76,29 @@ export interface ReferenceEditorProps { selectionStyle: 'full' | 'field' | 'hidden'; validation: RequiredReference; columnId: string; - updateLayer: ( - setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) - ) => void; + column?: GenericIndexPatternColumn; + incompleteColumn?: IncompleteColumn; currentIndexPattern: IndexPattern; - + functionLabel?: string; + fieldLabel?: string; + operationDefinitionMap: Record<string, GenericOperationDefinition>; + isInline?: boolean; existingFields: IndexPatternPrivateState['existingFields']; dateRange: DateRange; labelAppend?: EuiFormRowProps['labelAppend']; - dimensionGroups: VisualizationDimensionGroupConfig[]; isFullscreen: boolean; toggleFullscreen: () => void; setIsCloseable: (isCloseable: boolean) => void; + paramEditorCustomProps?: ParamEditorCustomProps; + paramEditorUpdater: ( + setter: + | IndexPatternLayer + | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) + | GenericIndexPatternColumn + ) => void; + onChooseField: (choice: FieldChoiceWithOperationType) => void; + onDeleteColumn: () => void; + onChooseFunction: (operationType: string, field?: IndexPatternField) => void; // Services uiSettings: IUiSettingsClient; @@ -69,39 +108,28 @@ export interface ReferenceEditorProps { data: DataPublicPluginStart; unifiedSearch: UnifiedSearchPublicPluginStart; dataViews: DataViewsPublicPluginStart; - paramEditorCustomProps?: ParamEditorCustomProps; } -export function ReferenceEditor(props: ReferenceEditorProps) { +export const ReferenceEditor = (props: ReferenceEditorProps) => { const { - layer, - layerId, - activeData, - columnId, - updateLayer, currentIndexPattern, existingFields, validation, selectionStyle, - dateRange, labelAppend, - dimensionGroups, - isFullscreen, - toggleFullscreen, - setIsCloseable, - paramEditorCustomProps, - ...services + column, + incompleteColumn, + functionLabel, + onChooseField, + onDeleteColumn, + onChooseFunction, + fieldLabel, + operationDefinitionMap, + isInline, } = props; - const column = layer.columns[columnId]; const selectedOperationDefinition = column && operationDefinitionMap[column.operationType]; - const ParamEditor = selectedOperationDefinition?.paramEditor; - - const incompleteInfo = layer.incompleteColumns ? layer.incompleteColumns[columnId] : undefined; - const incompleteOperation = incompleteInfo?.operationType; - const incompleteField = incompleteInfo?.sourceField ?? null; - // Basically the operation support matrix, but different validation const operationSupportMatrix: OperationSupportMatrix & { operationTypes: Set<OperationType>; @@ -111,7 +139,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { const operationByField: Partial<Record<string, Set<OperationType>>> = {}; const fieldByOperation: Partial<Record<OperationType, Set<string>>> = {}; Object.values(operationDefinitionMap) - .filter(({ hidden }) => !hidden) + .filter(({ hidden, allowAsReference }) => !hidden && allowAsReference) .sort((op1, op2) => { return op1.displayName.localeCompare(op2.displayName); }) @@ -152,230 +180,163 @@ export function ReferenceEditor(props: ReferenceEditorProps) { operationByField, fieldByOperation, }; - }, [currentIndexPattern, validation]); - - const functionOptions: Array<EuiComboBoxOptionOption<OperationType>> = Array.from( - operationSupportMatrix.operationTypes - ).map((operationType) => { - const def = operationDefinitionMap[operationType]; - const label = operationPanels[operationType].displayName; - const isCompatible = - !column || - (column && - hasField(column) && - def.input === 'field' && - operationSupportMatrix.fieldByOperation[operationType]?.has(column.sourceField)) || - (column && !hasField(column) && def.input !== 'field'); - - return { - label, - value: operationType, - className: 'lnsIndexPatternDimensionEditor__operation', - 'data-test-subj': `lns-indexPatternDimension-${operationType}${ - isCompatible ? '' : ' incompatible' - }`, - }; - }); - - function onChooseFunction(operationType: OperationType) { - if (column?.operationType === operationType) { - return; - } - const possibleFieldNames = operationSupportMatrix.fieldByOperation[operationType]; - if (column && 'sourceField' in column && possibleFieldNames?.has(column.sourceField)) { - // Reuse the current field if possible - updateLayer( - insertOrReplaceColumn({ - layer, - columnId, - op: operationType, - indexPattern: currentIndexPattern, - field: currentIndexPattern.getFieldByName(column.sourceField), - visualizationGroups: dimensionGroups, - }) - ); - } else { - // If reusing the field is impossible, we generally can't choose for the user. - // The one exception is if the field is the only possible field, like Count of Records. - const possibleField = - possibleFieldNames?.size === 1 - ? currentIndexPattern.getFieldByName(possibleFieldNames.values().next().value) - : undefined; - - updateLayer( - insertOrReplaceColumn({ - layer, - columnId, - op: operationType, - indexPattern: currentIndexPattern, - field: possibleField, - visualizationGroups: dimensionGroups, - }) - ); - } - trackUiEvent(`indexpattern_dimension_operation_${operationType}`); - return; - } + }, [currentIndexPattern, validation, operationDefinitionMap]); if (selectionStyle === 'hidden') { return null; } + const incompleteOperation = incompleteColumn?.operationType; + const incompleteField = incompleteColumn?.sourceField ?? null; + + const functionOptions = getFunctionOptions( + operationSupportMatrix, + operationDefinitionMap, + column + ); + const selectedOption = incompleteOperation - ? [functionOptions.find(({ value }) => value === incompleteOperation)!] + ? [functionOptions?.find(({ value }) => value === incompleteOperation)!] : column - ? [functionOptions.find(({ value }) => value === column.operationType)!] + ? [functionOptions?.find(({ value }) => value === column.operationType)!] : []; - // If the operationType is incomplete, the user needs to select a field- so - // the function is marked as valid. - const showOperationInvalid = !column && !Boolean(incompleteOperation); - // The field is invalid if the operation has been updated without a field, - // or if we are in a field-only mode but empty state - const showFieldInvalid = Boolean(incompleteOperation) || (selectionStyle === 'field' && !column); - // Check if the field still exists to protect from changes - const showFieldMissingInvalid = !currentIndexPattern.getFieldByName( - incompleteField ?? (column as FieldBasedIndexPatternColumn)?.sourceField - ); - // what about a field changing type and becoming invalid? // Let's say this change makes the indexpattern without any number field but the operation was set to a numeric operation. // At this point the ComboBox will crash. // Therefore check if the selectedOption is in functionOptions and in case fill it in as disabled option const showSelectionFunctionInvalid = Boolean(selectedOption.length && selectedOption[0] == null); if (showSelectionFunctionInvalid) { - const selectedOperationType = incompleteOperation || column.operationType; + const selectedOperationType = incompleteOperation || column?.operationType; const brokenFunctionOption = { - label: operationPanels[selectedOperationType].displayName, + label: selectedOperationType && operationDisplay[selectedOperationType].displayName, value: selectedOperationType, className: 'lnsIndexPatternDimensionEditor__operation', 'data-test-subj': `lns-indexPatternDimension-${selectedOperationType} incompatible`, - }; - functionOptions.push(brokenFunctionOption); + } as EuiComboBoxOptionOption<string>; + functionOptions?.push(brokenFunctionOption); selectedOption[0] = brokenFunctionOption; } + // If the operationType is incomplete, the user needs to select a field- so + // the function is marked as valid. + const showOperationInvalid = !column && !Boolean(incompleteOperation); + // The field is invalid if the operation has been updated without a field, + // or if we are in a field-only mode but empty state + const showFieldInvalid = Boolean(incompleteOperation) || (selectionStyle === 'field' && !column); + // Check if the field still exists to protect from changes + const showFieldMissingInvalid = !currentIndexPattern.getFieldByName( + incompleteField ?? (column as FieldBasedIndexPatternColumn)?.sourceField + ); + + const ParamEditor = selectedOperationDefinition?.paramEditor; + return ( - <div id={columnId}> - <div> - {selectionStyle !== 'field' ? ( - <> - <EuiFormRow - data-test-subj="indexPattern-subFunction-selection-row" - label={i18n.translate('xpack.lens.indexPattern.chooseSubFunction', { + <div> + {selectionStyle !== 'field' ? ( + <> + <FormRow + isInline={isInline} + data-test-subj="indexPattern-subFunction-selection-row" + label={ + functionLabel || + i18n.translate('xpack.lens.indexPattern.chooseSubFunction', { defaultMessage: 'Choose a sub-function', - })} + }) + } + fullWidth + isInvalid={showOperationInvalid || showSelectionFunctionInvalid} + > + <EuiComboBox fullWidth + compressed + isClearable={false} + data-test-subj="indexPattern-reference-function" + placeholder={ + functionLabel || + i18n.translate('xpack.lens.indexPattern.referenceFunctionPlaceholder', { + defaultMessage: 'Sub-function', + }) + } + options={functionOptions} isInvalid={showOperationInvalid || showSelectionFunctionInvalid} - > - <EuiComboBox - fullWidth - compressed - isClearable={false} - data-test-subj="indexPattern-reference-function" - placeholder={i18n.translate( - 'xpack.lens.indexPattern.referenceFunctionPlaceholder', - { - defaultMessage: 'Sub-function', - } - )} - options={functionOptions} - isInvalid={showOperationInvalid || showSelectionFunctionInvalid} - selectedOptions={selectedOption} - singleSelection={{ asPlainText: true }} - onChange={(choices) => { - if (choices.length === 0) { - updateLayer( - deleteColumn({ - layer, - columnId, - indexPattern: currentIndexPattern, - }) - ); - return; - } + selectedOptions={selectedOption} + singleSelection={{ asPlainText: true }} + onChange={(choices: Array<EuiComboBoxOptionOption<string>>) => { + if (choices.length === 0) { + return onDeleteColumn(); + } - trackUiEvent('indexpattern_dimension_field_changed'); + const operationType = choices[0].value!; + if (column?.operationType === operationType) { + return; + } + const possibleFieldNames = operationSupportMatrix.fieldByOperation[operationType]; - onChooseFunction(choices[0].value!); - }} - /> - </EuiFormRow> - <EuiSpacer size="s" /> - </> - ) : null} + const field = + column && 'sourceField' in column && possibleFieldNames?.has(column.sourceField) + ? currentIndexPattern.getFieldByName(column.sourceField) + : possibleFieldNames?.size === 1 + ? currentIndexPattern.getFieldByName(possibleFieldNames.values().next().value) + : undefined; - {!column || selectedOperationDefinition.input === 'field' ? ( - <EuiFormRow - data-test-subj="indexPattern-reference-field-selection-row" - label={i18n.translate('xpack.lens.indexPattern.chooseField', { - defaultMessage: 'Field', - })} - fullWidth - isInvalid={showFieldInvalid || showFieldMissingInvalid} - labelAppend={labelAppend} - > - <FieldSelect - fieldIsInvalid={showFieldInvalid || showFieldMissingInvalid} - currentIndexPattern={currentIndexPattern} - existingFields={existingFields} - operationByField={operationSupportMatrix.operationByField} - selectedOperationType={ - // Allows operation to be selected before creating a valid column - column ? column.operationType : incompleteOperation - } - selectedField={ - // Allows field to be selected - incompleteField ?? (column as FieldBasedIndexPatternColumn)?.sourceField - } - incompleteOperation={incompleteOperation} - markAllFieldsCompatible={selectionStyle === 'field'} - onDeleteColumn={() => { - updateLayer( - deleteColumn({ - layer, - columnId, - indexPattern: currentIndexPattern, - }) - ); - }} - onChoose={(choice) => { - updateLayer( - insertOrReplaceColumn({ - layer, - columnId, - indexPattern: currentIndexPattern, - op: choice.operationType, - field: currentIndexPattern.getFieldByName(choice.field), - visualizationGroups: dimensionGroups, - }) - ); + onChooseFunction(operationType, field); + trackUiEvent(`indexpattern_dimension_operation_${operationType}`); + return; }} /> - </EuiFormRow> - ) : null} + </FormRow> + <EuiSpacer size="s" /> + </> + ) : null} - {column && !incompleteInfo && ParamEditor && ( - <> - <ParamEditor - updateLayer={updateLayer} - currentColumn={column} - layer={layer} - layerId={layerId} - activeData={activeData} - columnId={columnId} - indexPattern={currentIndexPattern} - dateRange={dateRange} - operationDefinitionMap={operationDefinitionMap} - isFullscreen={isFullscreen} - toggleFullscreen={toggleFullscreen} - setIsCloseable={setIsCloseable} - paramEditorCustomProps={paramEditorCustomProps} - {...services} - /> - </> - )} - </div> + {!column || selectedOperationDefinition?.input === 'field' ? ( + <FormRow + isInline={isInline} + data-test-subj="indexPattern-reference-field-selection-row" + label={ + fieldLabel || + i18n.translate('xpack.lens.indexPattern.chooseField', { + defaultMessage: 'Field', + }) + } + fullWidth + isInvalid={showFieldInvalid || showFieldMissingInvalid} + labelAppend={labelAppend} + > + <FieldSelect + fieldIsInvalid={showFieldInvalid || showFieldMissingInvalid} + currentIndexPattern={currentIndexPattern} + existingFields={existingFields} + operationByField={operationSupportMatrix.operationByField} + selectedOperationType={ + // Allows operation to be selected before creating a valid column + column ? column.operationType : incompleteOperation + } + selectedField={ + // Allows field to be selected + incompleteField ?? (column as FieldBasedIndexPatternColumn)?.sourceField + } + incompleteOperation={incompleteOperation} + markAllFieldsCompatible={selectionStyle === 'field'} + onDeleteColumn={onDeleteColumn} + onChoose={onChooseField} + /> + </FormRow> + ) : null} + + {column && !incompleteColumn && ParamEditor && ( + <> + <EuiSpacer size="s" /> + <ParamEditor + {...props} + isReferenced={true} + operationDefinitionMap={operationDefinitionMap} + currentColumn={column} + indexPattern={props.currentIndexPattern} + /> + </> + )} </div> ); -} +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx index 20e5690f2f534..332f9664973af 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx @@ -101,6 +101,7 @@ export function TimeScaling({ <EuiFlexGroup gutterSize="s" alignItems="center"> <EuiFlexItem> <EuiSelect + fullWidth compressed options={Object.entries(unitSuffixesLong).map(([unit, text]) => ({ value: unit, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_shift.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_shift.tsx index 6900df51ccbba..b74e26cb24895 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_shift.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_shift.tsx @@ -163,7 +163,7 @@ export function TimeShift({ } isInvalid={Boolean(isLocalValueInvalid || localValueTooSmall || localValueNotMultiple)} > - <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}> <EuiFlexItem> <EuiComboBox fullWidth diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 5e26e49a8ef17..27c774ed2963e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -47,7 +47,7 @@ import { DragDrop, DragDropIdentifier } from '../drag_drop'; import { DatasourceDataPanelProps, DataType } from '../types'; import { BucketedAggregation, DOCUMENT_FIELD_NAME, FieldStatsResponse } from '../../common'; import { IndexPattern, IndexPatternField, DraggedField } from './types'; -import { LensFieldIcon } from './lens_field_icon'; +import { LensFieldIcon } from '../shared_components/field_picker/lens_field_icon'; import { trackUiEvent } from '../lens_ui_telemetry'; import { VisualizeGeoFieldButton } from './visualize_geo_field_button'; import { getVisualizeGeoFieldMessage } from '../utils'; 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 a37976f6d8069..3734d93b6071e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -42,6 +42,9 @@ import { DatatableColumn } from '@kbn/expressions-plugin'; jest.mock('./loader'); jest.mock('../id_generator'); jest.mock('./operations'); +jest.mock('./dimension_panel/reference_editor', () => ({ + ReferenceEditor: () => null, +})); const fieldsOne = [ { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts index d6429fb67e9a1..709bf87e2e6f0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/__mocks__/index.ts @@ -16,6 +16,7 @@ jest.spyOn(actualHelpers, 'copyColumn'); jest.spyOn(actualHelpers, 'insertOrReplaceColumn'); jest.spyOn(actualHelpers, 'insertNewColumn'); jest.spyOn(actualHelpers, 'replaceColumn'); +jest.spyOn(actualHelpers, 'adjustColumnReferencesForChangedColumn'); jest.spyOn(actualHelpers, 'getErrorMessages'); jest.spyOn(actualHelpers, 'getColumnOrder'); @@ -50,6 +51,7 @@ export const { isOperationAllowedAsReference, canTransition, isColumnValidAsReference, + adjustColumnReferencesForChangedColumn, getManagedColumnsFrom, } = actualHelpers; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx index cf96bcd11b788..c46b6954f7480 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/moving_average.tsx @@ -173,7 +173,7 @@ Example: Smooth a line of measurements: function MovingAverageParamEditor({ layer, - updateLayer, + paramEditorUpdater, currentColumn, columnId, }: ParamEditorProps<MovingAverageIndexPatternColumn>) { @@ -183,7 +183,7 @@ function MovingAverageParamEditor({ () => { if (!isValidNumber(inputValue, true, undefined, 1)) return; const inputNumber = parseInt(inputValue, 10); - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -207,6 +207,7 @@ function MovingAverageParamEditor({ isInvalid={!isValidNumber(inputValue)} > <EuiFieldNumber + fullWidth compressed value={inputValue} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setInputValue(e.target.value)} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx index b8f6aa433c5f7..7b3ccf8da067b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/cardinality.tsx @@ -65,11 +65,17 @@ export interface CardinalityIndexPatternColumn extends FieldBasedIndexPatternCol }; } -export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternColumn, 'field'> = { +export const cardinalityOperation: OperationDefinition< + CardinalityIndexPatternColumn, + 'field', + {}, + true +> = { type: OPERATION_TYPE, displayName: i18n.translate('xpack.lens.indexPattern.cardinality', { defaultMessage: 'Unique count', }), + allowAsReference: true, input: 'field', getPossibleOperationForField: ({ aggregationRestrictions, aggregatable, type }) => { if ( @@ -123,7 +129,7 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo layer, columnId, currentColumn, - updateLayer, + paramEditorUpdater, }: ParamEditorProps<CardinalityIndexPatternColumn>) => { return [ { @@ -141,7 +147,7 @@ export const cardinalityOperation: OperationDefinition<CardinalityIndexPatternCo }} checked={Boolean(currentColumn.params?.emptyAsNull)} onChange={() => { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx index 104b85651a876..014ff0f726cc7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/count.tsx @@ -40,7 +40,7 @@ export type CountIndexPatternColumn = FieldBasedIndexPatternColumn & { }; }; -export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field'> = { +export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field', {}, true> = { type: 'count', priority: 2, displayName: i18n.translate('xpack.lens.indexPattern.count', { @@ -52,6 +52,7 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field getInvalidFieldMessage(layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern), getDisallowedPreviousShiftMessage(layer, columnId), ]), + allowAsReference: true, onFieldChange: (oldColumn, field) => { return { ...oldColumn, @@ -112,7 +113,7 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field layer, columnId, currentColumn, - updateLayer, + paramEditorUpdater, }: ParamEditorProps<CountIndexPatternColumn>) => { return [ { @@ -130,7 +131,7 @@ export const countOperation: OperationDefinition<CountIndexPatternColumn, 'field }} checked={Boolean(currentColumn.params?.emptyAsNull)} onChange={() => { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx index d801387c30b29..73cb0a37ad563 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.test.tsx @@ -106,6 +106,14 @@ const defaultOptions = { isFullscreen: false, toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; describe('date_histogram', () => { @@ -310,7 +318,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -346,7 +354,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={secondLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={secondLayer.columns.col2 as DateHistogramIndexPatternColumn} indexPattern={indexPattern2} @@ -382,7 +390,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={thirdLayer} - updateLayer={jest.fn()} + paramEditorUpdater={jest.fn()} columnId="col1" currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn} indexPattern={indexPattern1} @@ -418,7 +426,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={thirdLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -459,7 +467,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={thirdLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn} indexPattern={{ ...indexPattern1, timeFieldName: undefined }} @@ -502,7 +510,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={thirdLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn} indexPattern={{ ...indexPattern1, timeFieldName: undefined }} @@ -544,7 +552,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={thirdLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn} indexPattern={{ ...indexPattern1, timeFieldName: 'other_timestamp' }} @@ -559,7 +567,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -581,7 +589,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={testLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={testLayer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -598,7 +606,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={testLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={testLayer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -615,7 +623,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={testLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={testLayer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -631,7 +639,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -655,7 +663,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={testLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={testLayer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -707,7 +715,7 @@ describe('date_histogram', () => { {...defaultOptions} layer={layer} indexPattern={indexPattern} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as DateHistogramIndexPatternColumn} /> @@ -741,7 +749,7 @@ describe('date_histogram', () => { <InlineOptions {...defaultOptions} layer={thirdLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={thirdLayer.columns.col1 as DateHistogramIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 3bbd329a39396..5ee246f09c2e5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -171,7 +171,7 @@ export const dateHistogramOperation: OperationDefinition< layer, columnId, currentColumn, - updateLayer, + paramEditorUpdater, dateRange, data, indexPattern, @@ -200,7 +200,7 @@ export const dateHistogramOperation: OperationDefinition< // updateColumnParam will be called async // store the checked value before the event pooling clears it const value = ev.target.checked; - updateLayer((newLayer) => + paramEditorUpdater((newLayer) => updateColumnParam({ layer: newLayer, columnId, @@ -209,7 +209,7 @@ export const dateHistogramOperation: OperationDefinition< }) ); }, - [columnId, updateLayer] + [columnId, paramEditorUpdater] ); const setInterval = useCallback( @@ -221,11 +221,11 @@ export const dateHistogramOperation: OperationDefinition< ? autoInterval : `${isCalendarInterval ? '1' : newInterval.value}${newInterval.unit || 'd'}`; - updateLayer((newLayer) => + paramEditorUpdater((newLayer) => updateColumnParam({ layer: newLayer, columnId, paramName: 'interval', value }) ); }, - [columnId, updateLayer] + [columnId, paramEditorUpdater] ); const options = (intervalOptions || []) @@ -323,7 +323,7 @@ export const dateHistogramOperation: OperationDefinition< const newValue = opts.length ? opts[0].key! : ''; setIntervalInput(newValue); if (newValue === autoInterval && currentColumn.params.ignoreTimeRange) { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -397,7 +397,7 @@ export const dateHistogramOperation: OperationDefinition< }); setIntervalInput(newFixedInterval); } - updateLayer(newLayer); + paramEditorUpdater(newLayer); }} compressed /> @@ -410,7 +410,7 @@ export const dateHistogramOperation: OperationDefinition< checked={Boolean(currentColumn.params.includeEmptyRows)} data-test-subj="indexPattern-include-empty-rows" onChange={() => { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx index 1bfa10be4107b..ef900ee1d7f8b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx @@ -36,6 +36,14 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; // mocking random id generator function @@ -304,7 +312,7 @@ describe('filters', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as FiltersIndexPatternColumn} /> @@ -357,7 +365,7 @@ describe('filters', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as FiltersIndexPatternColumn} /> @@ -382,7 +390,7 @@ describe('filters', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as FiltersIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx index d3eea6e223401..68798bd11aee5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx @@ -145,11 +145,11 @@ export const filtersOperation: OperationDefinition<FiltersIndexPatternColumn, 'n }).toAst(); }, - paramEditor: ({ layer, columnId, currentColumn, indexPattern, updateLayer, data }) => { + paramEditor: ({ layer, columnId, currentColumn, indexPattern, paramEditorUpdater }) => { const filters = currentColumn.params.filters; const setFilters = (newFilters: Filter[]) => - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -159,7 +159,7 @@ export const filtersOperation: OperationDefinition<FiltersIndexPatternColumn, 'n ); return ( - <EuiFormRow> + <EuiFormRow fullWidth> <FilterList filters={filters} setFilters={setFilters} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx index e912e345756f2..fcec1168a7474 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/editor/formula_editor.tsx @@ -85,7 +85,7 @@ const MemoizedFormulaEditor = React.memo(FormulaEditor); export function FormulaEditor({ layer, - updateLayer, + paramEditorUpdater, currentColumn, columnId, indexPattern, @@ -153,7 +153,7 @@ export function FormulaEditor({ setIsCloseable(true); // If the text is not synced, update the column. if (text !== currentColumn.params.formula) { - updateLayer( + paramEditorUpdater( (prevLayer) => insertOrReplaceFormulaColumn( columnId, @@ -183,7 +183,7 @@ export function FormulaEditor({ monaco.editor.setModelMarkers(editorModel.current, 'LENS', []); if (currentColumn.params.formula) { // Only submit if valid - updateLayer( + paramEditorUpdater( insertOrReplaceFormulaColumn( columnId, { @@ -232,7 +232,7 @@ export function FormulaEditor({ if (previousFormulaWasBroken || previousFormulaWasOkButNoData) { // If the formula is already broken, show the latest error message in the workspace if (currentColumn.params.formula !== text) { - updateLayer( + paramEditorUpdater( insertOrReplaceFormulaColumn( columnId, { @@ -314,7 +314,7 @@ export function FormulaEditor({ } ); - updateLayer(newLayer); + paramEditorUpdater(newLayer); const managedColumns = getManagedColumnsFrom(columnId, newLayer.columns); const markers: monaco.editor.IMarkerData[] = managedColumns diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx index c464ce0da027c..4ca172df112e5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/helpers.tsx @@ -12,7 +12,7 @@ import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn, } from './column_types'; -import { IndexPattern, IndexPatternField } from '../../types'; +import { IndexPattern, IndexPatternField, IndexPatternLayer } from '../../types'; import { hasField } from '../../pure_utils'; export function getInvalidFieldMessage( @@ -128,6 +128,15 @@ export function isColumnOfType<C extends GenericIndexPatternColumn>( return column.operationType === type; } +export const isColumn = ( + setter: + | GenericIndexPatternColumn + | IndexPatternLayer + | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) +): setter is GenericIndexPatternColumn => { + return 'operationType' in setter; +}; + export function isColumnFormatted( column: GenericIndexPatternColumn ): column is FormattedIndexPatternColumn { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index cdf2b0249529e..91a83d22f4a29 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -64,6 +64,7 @@ import { DateRange, LayerType } from '../../../../common'; import { rangeOperation } from './ranges'; import { IndexPatternDimensionEditorProps, OperationSupportMatrix } from '../../dimension_panel'; import type { OriginalColumn } from '../../to_expression'; +import { ReferenceEditorProps } from '../../dimension_panel/reference_editor'; export type { IncompleteColumn, @@ -160,12 +161,14 @@ export { staticValueOperation } from './static_value'; /** * Properties passed to the operation-specific part of the popover editor */ -export interface ParamEditorProps<C> { +export interface ParamEditorProps< + C, + U = IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) +> { currentColumn: C; layer: IndexPatternLayer; - updateLayer: ( - setter: IndexPatternLayer | ((prevLayer: IndexPatternLayer) => IndexPatternLayer) - ) => void; + paramEditorUpdater: (setter: U) => void; + ReferenceEditor?: (props: ReferenceEditorProps) => JSX.Element | null; toggleFullscreen: () => void; setIsCloseable: (isCloseable: boolean) => void; isFullscreen: boolean; @@ -183,6 +186,8 @@ export interface ParamEditorProps<C> { activeData?: IndexPatternDimensionEditorProps['activeData']; operationDefinitionMap: Record<string, GenericOperationDefinition>; paramEditorCustomProps?: ParamEditorCustomProps; + existingFields: Record<string, Record<string, boolean>>; + isReferenced?: boolean; } export interface FieldInputProps<C> { @@ -227,7 +232,11 @@ export interface AdvancedOption { helpPopup?: string | null; } -interface BaseOperationDefinitionProps<C extends BaseIndexPatternColumn, P = {}> { +interface BaseOperationDefinitionProps< + C extends BaseIndexPatternColumn, + AR extends boolean, + P = {} +> { type: C['operationType']; /** * The priority of the operation. If multiple operations are possible in @@ -258,7 +267,10 @@ interface BaseOperationDefinitionProps<C extends BaseIndexPatternColumn, P = {}> /** * React component for operation specific settings shown in the flyout editor */ - paramEditor?: React.ComponentType<ParamEditorProps<C>>; + allowAsReference?: AR; + paramEditor?: React.ComponentType< + AR extends true ? ParamEditorProps<C, GenericIndexPatternColumn> : ParamEditorProps<C> + >; getAdvancedOptions?: (params: ParamEditorProps<C>) => AdvancedOption[] | undefined; /** * Returns true if the `column` can also be used on `newIndexPattern`. @@ -498,7 +510,8 @@ interface FieldBasedOperationDefinition<C extends BaseIndexPatternColumn, P = {} indexPattern: IndexPattern, layer: IndexPatternLayer, uiSettings: IUiSettingsClient, - orderedColumnIds: string[] + orderedColumnIds: string[], + operationDefinitionMap?: Record<string, GenericOperationDefinition> ) => ExpressionAstFunction; /** * Validate that the operation has the right preconditions in the state. For example: @@ -646,8 +659,9 @@ interface OperationDefinitionMap<C extends BaseIndexPatternColumn, P = {}> { export type OperationDefinition< C extends BaseIndexPatternColumn, Input extends keyof OperationDefinitionMap<C>, - P = {} -> = BaseOperationDefinitionProps<C> & OperationDefinitionMap<C, P>[Input]; + P = {}, + AR extends boolean = false +> = BaseOperationDefinitionProps<C, AR> & OperationDefinitionMap<C, P>[Input]; /** * A union type of all available operation types. The operation type is a unique id of an operation. diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx index 242bdeaa677cb..f2248fcdf36c9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.test.tsx @@ -40,6 +40,14 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; describe('last_value', () => { @@ -642,6 +650,13 @@ describe('last_value', () => { return this.showArrayValuesSwitch.prop('disabled'); } + public get arrayValuesSwitchNotExisiting() { + return ( + this._instance.find('[data-test-subj="lns-indexPattern-lastValue-showArrayValues"]') + .length === 0 + ); + } + changeSortFieldOptions(options: Array<{ label: string; value: string }>) { this.sortField.find(EuiComboBox).prop('onChange')!([ { label: 'datefield2', value: 'datefield2' }, @@ -659,7 +674,7 @@ describe('last_value', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col2 as LastValueIndexPatternColumn} /> @@ -676,7 +691,7 @@ describe('last_value', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as LastValueIndexPatternColumn} /> @@ -685,16 +700,10 @@ describe('last_value', () => { new Harness(instance).changeSortFieldOptions([{ label: 'datefield2', value: 'datefield2' }]); expect(updateLayerSpy).toHaveBeenCalledWith({ - ...layer, - columns: { - ...layer.columns, - col2: { - ...layer.columns.col2, - params: { - ...(layer.columns.col2 as LastValueIndexPatternColumn).params, - sortField: 'datefield2', - }, - }, + ...layer.columns.col2, + params: { + ...(layer.columns.col2 as LastValueIndexPatternColumn).params, + sortField: 'datefield2', }, }); }); @@ -707,7 +716,7 @@ describe('last_value', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as LastValueIndexPatternColumn} /> @@ -718,27 +727,29 @@ describe('last_value', () => { harness.toggleShowArrayValues(); expect(updateLayerSpy).toHaveBeenCalledWith({ - ...layer, - columns: { - ...layer.columns, - col2: { - ...layer.columns.col2, - params: { - ...(layer.columns.col2 as LastValueIndexPatternColumn).params, - showArrayValues: true, - }, - }, + ...layer.columns.col2, + params: { + ...(layer.columns.col2 as LastValueIndexPatternColumn).params, + showArrayValues: true, }, }); // have to do this manually, but it happens automatically in the app - const newLayer = updateLayerSpy.mock.calls[0][0]; + const newColumn = updateLayerSpy.mock.calls[0][0]; + const newLayer = { + ...layer, + columns: { + ...layer.columns, + col2: newColumn, + }, + }; instance.setProps({ layer: newLayer, currentColumn: newLayer.columns.col2 }); expect(harness.showingTopValuesWarning).toBeTruthy(); }); it('should not warn user when top-values not in use', () => { + // todo: move to dimension editor const updateLayerSpy = jest.fn(); const localLayer = { ...layer, @@ -754,7 +765,7 @@ describe('last_value', () => { <InlineOptions {...defaultProps} layer={localLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as LastValueIndexPatternColumn} /> @@ -764,7 +775,15 @@ describe('last_value', () => { harness.toggleShowArrayValues(); // have to do this manually, but it happens automatically in the app - const newLayer = updateLayerSpy.mock.calls[0][0]; + const newColumn = updateLayerSpy.mock.calls[0][0]; + const newLayer = { + ...localLayer, + columns: { + ...localLayer.columns, + col2: newColumn, + }, + }; + instance.setProps({ layer: newLayer, currentColumn: newLayer.columns.col2 }); expect(harness.showingTopValuesWarning).toBeFalsy(); @@ -778,7 +797,7 @@ describe('last_value', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as LastValueIndexPatternColumn} /> @@ -786,6 +805,21 @@ describe('last_value', () => { expect(new Harness(instance).showArrayValuesSwitchDisabled).toBeTruthy(); }); + it('should not display an array for the last value if the column is referenced', () => { + const updateLayerSpy = jest.fn(); + const instance = shallow( + <InlineOptions + {...defaultProps} + isReferenced={true} + layer={layer} + paramEditorUpdater={updateLayerSpy} + columnId="col1" + currentColumn={layer.columns.col2 as LastValueIndexPatternColumn} + /> + ); + + expect(new Harness(instance).arrayValuesSwitchNotExisiting).toBeTruthy(); + }); }); }); @@ -829,6 +863,7 @@ describe('last_value', () => { 'Field notExisting was not found', ]); }); + it('shows error message if the sortField does not exist in index pattern', () => { errorLayer = { ...errorLayer, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx index 40b4d53154ba7..0af5ed4428ef7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/last_value.tsx @@ -20,7 +20,6 @@ import { buildExpressionFunction } from '@kbn/expressions-plugin/public'; import { OperationDefinition } from '.'; import { FieldBasedIndexPatternColumn, ValueFormatConfig } from './column_types'; import { IndexPatternField, IndexPattern } from '../../types'; -import { adjustColumnReferencesForChangedColumn, updateColumnParam } from '../layer_helpers'; import { DataType } from '../../../types'; import { getFormatFromPreviousColumn, @@ -31,6 +30,7 @@ import { import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; import { isScriptedField } from './terms/helpers'; +import { FormRow } from './shared_components/form_row'; function ofName(name: string, timeShift: string | undefined) { return adjustTimeScaleLabelSuffix( @@ -122,7 +122,8 @@ function getExistsFilter(field: string) { export const lastValueOperation: OperationDefinition< LastValueIndexPatternColumn, 'field', - Partial<LastValueIndexPatternColumn['params']> + Partial<LastValueIndexPatternColumn['params']>, + true > = { type: 'last_value', displayName: i18n.translate('xpack.lens.indexPattern.lastValue', { @@ -257,8 +258,22 @@ export const lastValueOperation: OperationDefinition< supportedTypes.has(newField.type) ); }, + allowAsReference: true, + paramEditor: ({ + layer, + paramEditorUpdater, + currentColumn, + indexPattern, + isReferenced, + paramEditorCustomProps, + }) => { + const { labels, isInline } = paramEditorCustomProps || {}; + const sortByFieldLabel = + labels?.[0] || + i18n.translate('xpack.lens.indexPattern.lastValue.sortField', { + defaultMessage: 'Sort by date field', + }); - paramEditor: ({ layer, updateLayer, columnId, currentColumn, indexPattern }) => { const dateFields = getDateFields(indexPattern); const isSortFieldInvalid = !!getInvalidSortFieldMessage( currentColumn.params.sortField, @@ -270,27 +285,20 @@ export const lastValueOperation: OperationDefinition< ); const setShowArrayValues = (use: boolean) => { - let updatedLayer = updateColumnParam({ - layer, - columnId, - paramName: 'showArrayValues', - value: use, - }); - - updatedLayer = { - ...updatedLayer, - columns: adjustColumnReferencesForChangedColumn(updatedLayer, columnId), - }; - - updateLayer(updatedLayer); + return paramEditorUpdater({ + ...currentColumn, + params: { + ...currentColumn.params, + showArrayValues: use, + }, + } as LastValueIndexPatternColumn); }; return ( <> - <EuiFormRow - label={i18n.translate('xpack.lens.indexPattern.lastValue.sortField', { - defaultMessage: 'Sort by date field', - })} + <FormRow + isInline={isInline} + label={sortByFieldLabel} display="rowCompressed" fullWidth error={i18n.translate('xpack.lens.indexPattern.sortField.invalid', { @@ -302,14 +310,13 @@ export const lastValueOperation: OperationDefinition< placeholder={i18n.translate('xpack.lens.indexPattern.lastValue.sortFieldPlaceholder', { defaultMessage: 'Sort field', })} + fullWidth compressed isClearable={false} data-test-subj="lns-indexPattern-lastValue-sortField" isInvalid={isSortFieldInvalid} singleSelection={{ asPlainText: true }} - aria-label={i18n.translate('xpack.lens.indexPattern.lastValue.sortField', { - defaultMessage: 'Sort by date field', - })} + aria-label={sortByFieldLabel} options={dateFields?.map((field: IndexPatternField) => { return { value: field.name, @@ -320,14 +327,13 @@ export const lastValueOperation: OperationDefinition< if (choices.length === 0) { return; } - updateLayer( - updateColumnParam({ - layer, - columnId, - paramName: 'sortField', - value: choices[0].value, - }) - ); + return paramEditorUpdater({ + ...currentColumn, + params: { + ...currentColumn.params, + sortField: choices[0].value, + }, + } as LastValueIndexPatternColumn); }} selectedOptions={ (currentColumn.params?.sortField @@ -342,41 +348,43 @@ export const lastValueOperation: OperationDefinition< : []) as unknown as EuiComboBoxOptionOption[] } /> - </EuiFormRow> - <EuiFormRow - error={i18n.translate( - 'xpack.lens.indexPattern.lastValue.showArrayValuesWithTopValuesWarning', - { - defaultMessage: - 'When you show array values, you are unable to use this field to rank Top values.', - } - )} - isInvalid={currentColumn.params.showArrayValues && usingTopValues} - display="rowCompressed" - fullWidth - data-test-subj="lns-indexPattern-lastValue-showArrayValues" - > - <EuiToolTip - content={i18n.translate( - 'xpack.lens.indexPattern.lastValue.showArrayValuesExplanation', + </FormRow> + {!isReferenced && ( + <EuiFormRow + error={i18n.translate( + 'xpack.lens.indexPattern.lastValue.showArrayValuesWithTopValuesWarning', { defaultMessage: - 'Displays all values associated with this field in each last document.', + 'When you show array values, you are unable to use this field to rank top values.', } )} - position="left" + isInvalid={currentColumn.params.showArrayValues && usingTopValues} + display="rowCompressed" + fullWidth + data-test-subj="lns-indexPattern-lastValue-showArrayValues" > - <EuiSwitch - label={i18n.translate('xpack.lens.indexPattern.lastValue.showArrayValues', { - defaultMessage: 'Show array values', - })} - compressed={true} - checked={Boolean(currentColumn.params.showArrayValues)} - disabled={isScriptedField(currentColumn.sourceField, indexPattern)} - onChange={() => setShowArrayValues(!currentColumn.params.showArrayValues)} - /> - </EuiToolTip> - </EuiFormRow> + <EuiToolTip + content={i18n.translate( + 'xpack.lens.indexPattern.lastValue.showArrayValuesExplanation', + { + defaultMessage: + 'Displays all values associated with this field in each last document.', + } + )} + position="left" + > + <EuiSwitch + label={i18n.translate('xpack.lens.indexPattern.lastValue.showArrayValues', { + defaultMessage: 'Show array values', + })} + compressed={true} + checked={Boolean(currentColumn.params.showArrayValues)} + disabled={isScriptedField(currentColumn.sourceField, indexPattern)} + onChange={() => setShowArrayValues(!currentColumn.params.showArrayValues)} + /> + </EuiToolTip> + </EuiFormRow> + )} </> ); }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx index 10c4310e820b2..b58ffdf8c8a1d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/metrics.tsx @@ -81,6 +81,7 @@ function buildMetricOperation<T extends MetricColumn<string>>({ return { type, + allowAsReference: true, priority, displayName, description, @@ -142,7 +143,12 @@ function buildMetricOperation<T extends MetricColumn<string>>({ sourceField: field.name, }; }, - getAdvancedOptions: ({ layer, columnId, currentColumn, updateLayer }: ParamEditorProps<T>) => { + getAdvancedOptions: ({ + layer, + columnId, + currentColumn, + paramEditorUpdater, + }: ParamEditorProps<T>) => { if (!hideZeroOption) return []; return [ { @@ -160,7 +166,7 @@ function buildMetricOperation<T extends MetricColumn<string>>({ }} checked={Boolean(currentColumn.params?.emptyAsNull)} onChange={() => { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -221,7 +227,7 @@ Example: Get the {metric} of price for orders from the UK: }), }, shiftable: true, - } as OperationDefinition<T, 'field'>; + } as OperationDefinition<T, 'field', {}, true>; } export type SumIndexPatternColumn = MetricColumn<'sum'>; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx index 08afcc447eec6..a359ae0b89820 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.test.tsx @@ -56,6 +56,14 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; describe('percentile', () => { @@ -715,7 +723,7 @@ describe('percentile', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as PercentileIndexPatternColumn} /> @@ -732,7 +740,7 @@ describe('percentile', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as PercentileIndexPatternColumn} /> @@ -752,17 +760,11 @@ describe('percentile', () => { instance.update(); expect(updateLayerSpy).toHaveBeenCalledWith({ - ...layer, - columns: { - ...layer.columns, - col2: { - ...layer.columns.col2, - params: { - percentile: 27, - }, - label: '27th percentile of a', - }, + ...layer.columns.col2, + params: { + percentile: 27, }, + label: '27th percentile of a', }); }); @@ -772,7 +774,7 @@ describe('percentile', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as PercentileIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx index a313b03d34e1b..c7e028dfaea7e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFormRow, EuiRange, EuiRangeProps } from '@elastic/eui'; +import { EuiFieldNumber, EuiRange } from '@elastic/eui'; import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { AggFunctionsMapping, METRIC_TYPES } from '@kbn/data-plugin/public'; @@ -29,6 +29,7 @@ import { FieldBasedIndexPatternColumn } from './column_types'; import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { useDebouncedValue } from '../../../shared_components'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; +import { FormRow } from './shared_components'; export interface PercentileIndexPatternColumn extends FieldBasedIndexPatternColumn { operationType: 'percentile'; @@ -64,9 +65,11 @@ const supportedFieldTypes = ['number', 'histogram']; export const percentileOperation: OperationDefinition< PercentileIndexPatternColumn, 'field', - { percentile: number } + { percentile: number }, + true > = { type: 'percentile', + allowAsReference: true, displayName: i18n.translate('xpack.lens.indexPattern.percentile', { defaultMessage: 'Percentile', }), @@ -268,12 +271,17 @@ export const percentileOperation: OperationDefinition< getDisallowedPreviousShiftMessage(layer, columnId), ]), paramEditor: function PercentileParamEditor({ - layer, - updateLayer, + paramEditorUpdater, currentColumn, - columnId, indexPattern, + paramEditorCustomProps, }) { + const { labels, isInline } = paramEditorCustomProps || {}; + const percentileLabel = + labels?.[0] || + i18n.translate('xpack.lens.indexPattern.percentile.percentileValue', { + defaultMessage: 'Percentile', + }); const onChange = useCallback( (value) => { if ( @@ -282,29 +290,23 @@ export const percentileOperation: OperationDefinition< ) { return; } - updateLayer({ - ...layer, - columns: { - ...layer.columns, - [columnId]: { - ...currentColumn, - label: currentColumn.customLabel - ? currentColumn.label - : ofName( - indexPattern.getFieldByName(currentColumn.sourceField)?.displayName || - currentColumn.sourceField, - Number(value), - currentColumn.timeShift - ), - params: { - ...currentColumn.params, - percentile: Number(value), - }, - } as PercentileIndexPatternColumn, + paramEditorUpdater({ + ...currentColumn, + label: currentColumn.customLabel + ? currentColumn.label + : ofName( + indexPattern.getFieldByName(currentColumn.sourceField)?.displayName || + currentColumn.sourceField, + Number(value), + currentColumn.timeShift + ), + params: { + ...currentColumn.params, + percentile: Number(value), }, - }); + } as PercentileIndexPatternColumn); }, - [updateLayer, layer, columnId, currentColumn, indexPattern] + [paramEditorUpdater, currentColumn, indexPattern] ); const { inputValue, handleInputChange: handleInputChangeWithoutValidation } = useDebouncedValue< string | undefined @@ -314,16 +316,15 @@ export const percentileOperation: OperationDefinition< }); const inputValueIsValid = isValidNumber(inputValue, true, 99, 1); - const handleInputChange: EuiRangeProps['onChange'] = useCallback( + const handleInputChange = useCallback( (e) => handleInputChangeWithoutValidation(String(e.currentTarget.value)), [handleInputChangeWithoutValidation] ); return ( - <EuiFormRow - label={i18n.translate('xpack.lens.indexPattern.percentile.percentileValue', { - defaultMessage: 'Percentile', - })} + <FormRow + isInline={isInline} + label={percentileLabel} data-test-subj="lns-indexPattern-percentile-form" display="rowCompressed" fullWidth @@ -335,20 +336,33 @@ export const percentileOperation: OperationDefinition< }) } > - <EuiRange - data-test-subj="lns-indexPattern-percentile-input" - compressed - value={inputValue ?? ''} - min={1} - max={99} - step={1} - onChange={handleInputChange} - showInput - aria-label={i18n.translate('xpack.lens.indexPattern.percentile.percentileValue', { - defaultMessage: 'Percentile', - })} - /> - </EuiFormRow> + {isInline ? ( + <EuiFieldNumber + fullWidth + data-test-subj="lns-indexPattern-percentile-input" + compressed + value={inputValue ?? ''} + min={1} + max={99} + step={1} + onChange={handleInputChange} + aria-label={percentileLabel} + /> + ) : ( + <EuiRange + fullWidth + data-test-subj="lns-indexPattern-percentile-input" + compressed + value={inputValue ?? ''} + min={1} + max={99} + step={1} + onChange={handleInputChange} + showInput + aria-label={percentileLabel} + /> + )} + </FormRow> ); }, documentation: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx index a7dbeedee633b..62c0bbd45be6c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.test.tsx @@ -50,6 +50,14 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; describe('percentile ranks', () => { @@ -274,7 +282,7 @@ describe('percentile ranks', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as PercentileRanksIndexPatternColumn} /> @@ -291,7 +299,7 @@ describe('percentile ranks', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as PercentileRanksIndexPatternColumn} /> @@ -310,17 +318,11 @@ describe('percentile ranks', () => { instance.update(); expect(updateLayerSpy).toHaveBeenCalledWith({ - ...layer, - columns: { - ...layer.columns, - col2: { - ...layer.columns.col2, - params: { - value: 103, - }, - label: 'Percentile rank (103) of a', - }, + ...layer.columns.col2, + params: { + value: 103, }, + label: 'Percentile rank (103) of a', }); }); @@ -330,7 +332,7 @@ describe('percentile ranks', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as PercentileRanksIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx index f153b2aca669b..61a7a33cdbd73 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/percentile_ranks.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFormRow, EuiFieldNumberProps, EuiFieldNumber } from '@elastic/eui'; +import { EuiFieldNumberProps, EuiFieldNumber } from '@elastic/eui'; import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { AggFunctionsMapping } from '@kbn/data-plugin/public'; @@ -24,6 +24,7 @@ import { FieldBasedIndexPatternColumn } from './column_types'; import { adjustTimeScaleLabelSuffix } from '../time_scale_utils'; import { useDebouncedValue } from '../../../shared_components'; import { getDisallowedPreviousShiftMessage } from '../../time_shift_utils'; +import { FormRow } from './shared_components'; export interface PercentileRanksIndexPatternColumn extends FieldBasedIndexPatternColumn { operationType: 'percentile_rank'; @@ -52,9 +53,11 @@ const supportedFieldTypes = ['number', 'histogram']; export const percentileRanksOperation: OperationDefinition< PercentileRanksIndexPatternColumn, 'field', - { value: number } + { value: number }, + true > = { type: 'percentile_rank', + allowAsReference: true, displayName: i18n.translate('xpack.lens.indexPattern.percentileRank', { defaultMessage: 'Percentile rank', }), @@ -143,40 +146,39 @@ export const percentileRanksOperation: OperationDefinition< getDisallowedPreviousShiftMessage(layer, columnId), ]), paramEditor: function PercentileParamEditor({ - layer, - updateLayer, + paramEditorUpdater, currentColumn, - columnId, indexPattern, + paramEditorCustomProps, }) { + const { labels, isInline } = paramEditorCustomProps || {}; + const percentileRanksLabel = + labels?.[0] || + i18n.translate('xpack.lens.indexPattern.percentile.percentileRanksValue', { + defaultMessage: 'Percentile ranks value', + }); const onChange = useCallback( (value) => { if (!isValidNumber(value) || Number(value) === currentColumn.params.value) { return; } - updateLayer({ - ...layer, - columns: { - ...layer.columns, - [columnId]: { - ...currentColumn, - label: currentColumn.customLabel - ? currentColumn.label - : ofName( - indexPattern.getFieldByName(currentColumn.sourceField)?.displayName || - currentColumn.sourceField, - Number(value), - currentColumn.timeShift - ), - params: { - ...currentColumn.params, - value: Number(value), - }, - } as PercentileRanksIndexPatternColumn, + paramEditorUpdater({ + ...currentColumn, + label: currentColumn.customLabel + ? currentColumn.label + : ofName( + indexPattern.getFieldByName(currentColumn.sourceField)?.displayName || + currentColumn.sourceField, + Number(value), + currentColumn.timeShift + ), + params: { + ...currentColumn.params, + value: Number(value), }, - }); + } as PercentileRanksIndexPatternColumn); }, - [updateLayer, layer, columnId, currentColumn, indexPattern] + [paramEditorUpdater, currentColumn, indexPattern] ); const { inputValue, handleInputChange: handleInputChangeWithoutValidation } = useDebouncedValue< string | undefined @@ -197,10 +199,9 @@ export const percentileRanksOperation: OperationDefinition< ); return ( - <EuiFormRow - label={i18n.translate('xpack.lens.indexPattern.percentile.percentileRanksValue', { - defaultMessage: 'Percentile ranks value', - })} + <FormRow + isInline={isInline} + label={percentileRanksLabel} data-test-subj="lns-indexPattern-percentile_ranks-form" display="rowCompressed" fullWidth @@ -213,16 +214,15 @@ export const percentileRanksOperation: OperationDefinition< } > <EuiFieldNumber + fullWidth data-test-subj="lns-indexPattern-percentile_ranks-input" compressed value={inputValue ?? ''} onChange={handleInputChange} step="any" - aria-label={i18n.translate('xpack.lens.indexPattern.percentile.percentileRanksValue', { - defaultMessage: 'Percentile ranks value', - })} + aria-label={percentileRanksLabel} /> - </EuiFormRow> + </FormRow> ); }, documentation: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx index f0611f5ec194e..60d5a76a3085e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx @@ -246,6 +246,7 @@ export const AdvancedRangeEditor = ({ return ( <EuiFormRow + fullWidth label={i18n.translate('xpack.lens.indexPattern.ranges.customRanges', { defaultMessage: 'Ranges', })} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx index 30f883083072b..9548d9473e656 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.test.tsx @@ -83,6 +83,14 @@ const defaultOptions = { storage: {} as IStorageWrapper, uiSettings: uiSettingsMock, savedObjectsClient: {} as SavedObjectsClientContract, + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, dateRange: { fromDate: 'now-1y', toDate: 'now', @@ -374,7 +382,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -390,7 +398,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -433,7 +441,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -503,7 +511,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -519,7 +527,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -539,7 +547,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={ { @@ -565,7 +573,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -620,7 +628,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -675,7 +683,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -722,7 +730,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -772,7 +780,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -810,7 +818,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -842,7 +850,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} indexPattern={{ @@ -872,7 +880,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} indexPattern={{ @@ -896,7 +904,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> @@ -916,7 +924,7 @@ describe('ranges', () => { <InlineOptions {...defaultOptions} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as RangeIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index 11754e1f90005..06db3221bde34 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -180,7 +180,7 @@ export const rangeOperation: OperationDefinition<RangeIndexPatternColumn, 'field layer, columnId, currentColumn, - updateLayer, + paramEditorUpdater, indexPattern, uiSettings, data, @@ -208,7 +208,7 @@ export const rangeOperation: OperationDefinition<RangeIndexPatternColumn, 'field // Used to change one param at the time const setParam: UpdateParamsFnType = (paramName, value) => { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -226,7 +226,7 @@ export const rangeOperation: OperationDefinition<RangeIndexPatternColumn, 'field newMode === MODES.Range ? { id: 'range', params: { template: 'arrow_right', replaceInfinity: true } } : undefined; - updateLayer({ + paramEditorUpdater({ ...layer, columns: { ...layer.columns, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/form_row.scss b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/form_row.scss new file mode 100644 index 0000000000000..accbb4060e797 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/form_row.scss @@ -0,0 +1,3 @@ +.lnsIndexPatternDimensionEditor__labelCustomRank { + min-width: 96px; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/form_row.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/form_row.tsx new file mode 100644 index 0000000000000..3961ff1b8a0de --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/form_row.tsx @@ -0,0 +1,30 @@ +/* + * 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 { EuiFormLabel, EuiFormRow, EuiFormRowProps } from '@elastic/eui'; +import './form_row.scss'; + +type FormRowProps = EuiFormRowProps & { isInline?: boolean }; + +export const FormRow = ({ children, label, isInline, ...props }: FormRowProps) => { + return !isInline ? ( + <EuiFormRow {...props} label={label}> + {children} + </EuiFormRow> + ) : ( + <div data-test-subj={props['data-test-subj']}> + {React.cloneElement(children, { + prepend: ( + <EuiFormLabel className="lnsIndexPatternDimensionEditor__labelCustomRank"> + {label} + </EuiFormLabel> + ), + })} + </div> + ); +}; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx index b2f409c9dcbd0..47cc121be095b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/index.tsx @@ -7,3 +7,4 @@ export * from './label_input'; export * from './buckets'; +export * from './form_row'; 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 df96b02ba2c95..ea870e68f563b 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 @@ -49,6 +49,14 @@ const defaultProps = { toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + existingFields: { + my_index_pattern: { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; describe('static_value', () => { @@ -340,7 +348,7 @@ describe('static_value', () => { <ParamEditor {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as StaticValueIndexPatternColumn} /> @@ -371,7 +379,7 @@ describe('static_value', () => { <ParamEditor {...defaultProps} layer={zeroLayer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={zeroLayer.columns.col2 as StaticValueIndexPatternColumn} /> @@ -387,7 +395,7 @@ describe('static_value', () => { <ParamEditor {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as StaticValueIndexPatternColumn} /> @@ -428,7 +436,7 @@ describe('static_value', () => { <ParamEditor {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col2" currentColumn={layer.columns.col2 as StaticValueIndexPatternColumn} /> 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 5642c06c6b642..bb9b36a3d097b 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 @@ -153,7 +153,7 @@ export const staticValueOperation: OperationDefinition< }, paramEditor: function StaticValueEditor({ - updateLayer, + paramEditorUpdater, currentColumn, columnId, activeData, @@ -168,7 +168,7 @@ export const staticValueOperation: OperationDefinition< } // Because of upstream specific UX flows, we need fresh layer state here // so need to use the updater pattern - updateLayer((newLayer) => { + paramEditorUpdater((newLayer) => { const newColumn = newLayer.columns[columnId] as StaticValueIndexPatternColumn; return { ...newLayer, @@ -186,7 +186,7 @@ export const staticValueOperation: OperationDefinition< }; }); }, - [columnId, updateLayer, currentColumn?.params?.value] + [columnId, paramEditorUpdater, currentColumn?.params?.value] ); // Pick the data from the current activeData (to be used when the current operation is not static_value) @@ -216,9 +216,10 @@ export const staticValueOperation: OperationDefinition< return ( <div className="lnsIndexPatternDimensionEditor__section lnsIndexPatternDimensionEditor__section--padded lnsIndexPatternDimensionEditor__section--shaded"> - <EuiFormLabel>{paramEditorCustomProps?.label || defaultLabel}</EuiFormLabel> + <EuiFormLabel>{paramEditorCustomProps?.labels?.[0] || defaultLabel}</EuiFormLabel> <EuiSpacer size="s" /> <EuiFieldNumber + fullWidth data-test-subj="lns-indexPattern-static_value-input" compressed value={inputValue ?? ''} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 62aed475df42a..31effec454efd 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, @@ -21,12 +21,17 @@ import { import { uniq } from 'lodash'; import { AggFunctionsMapping } from '@kbn/data-plugin/public'; import { buildExpressionFunction } from '@kbn/expressions-plugin/public'; +import { DOCUMENT_FIELD_NAME } from '../../../../../common'; import { insertOrReplaceColumn, updateColumnParam, updateDefaultLabels } from '../../layer_helpers'; -import type { DataType } from '../../../../types'; +import type { DataType, OperationMetadata } from '../../../../types'; import { OperationDefinition } from '..'; -import { FieldBasedIndexPatternColumn } from '../column_types'; +import { + FieldBasedIndexPatternColumn, + GenericIndexPatternColumn, + IncompleteColumn, +} from '../column_types'; import { ValuesInput } from './values_input'; -import { getInvalidFieldMessage } from '../helpers'; +import { getInvalidFieldMessage, isColumn } from '../helpers'; import { FieldInputs, getInputFieldErrorMessage, MAX_MULTI_FIELDS_SIZE } from './field_inputs'; import { FieldInput as FieldInputBase, @@ -226,7 +231,15 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field }, }; }, - toEsAggsFn: (column, columnId, _indexPattern, layer, uiSettings, orderedColumnIds) => { + toEsAggsFn: ( + column, + columnId, + _indexPattern, + layer, + uiSettings, + orderedColumnIds, + operationDefinitionMap + ) => { if (column.params?.orderBy.type === 'rare') { return buildExpressionFunction<AggFunctionsMapping['aggRareTerms']>('aggRareTerms', { id: columnId, @@ -236,7 +249,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field max_doc_count: column.params.orderBy.maxDocCount, }).toAst(); } - let orderBy = '_key'; + let orderBy: string = '_key'; if (column.params?.orderBy.type === 'column') { const orderColumn = layer.columns[column.params.orderBy.columnId]; @@ -254,6 +267,29 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field ? Math.max(1000, column.params.size * 1.5 + 10) : undefined; + const orderAggColumn = column.params.orderAgg; + let orderAgg; + if (orderAggColumn) { + orderBy = 'custom'; + const def = operationDefinitionMap?.[orderAggColumn?.operationType]; + if (def && 'toEsAggsFn' in def) { + orderAgg = [ + { + type: 'expression' as const, + chain: [ + def.toEsAggsFn( + orderAggColumn, + `${columnId}-orderAgg`, + _indexPattern, + layer, + uiSettings, + orderedColumnIds + ), + ], + }, + ]; + } + } if (column.params?.secondaryFields?.length) { return buildExpressionFunction<AggFunctionsMapping['aggMultiTerms']>('aggMultiTerms', { id: columnId, @@ -262,6 +298,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field fields: [column.sourceField, ...column.params.secondaryFields], orderBy, order: column.params.orderDirection, + orderAgg, size: column.params.size, shardSize, otherBucket: Boolean(column.params.otherBucket), @@ -270,6 +307,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field }), }).toAst(); } + return buildExpressionFunction<AggFunctionsMapping['aggTerms']>('aggTerms', { id: columnId, enabled: true, @@ -277,6 +315,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field field: column.sourceField, orderBy, order: column.params.orderDirection, + orderAgg, size: column.params.size, shardSize, otherBucket: Boolean(column.params.otherBucket), @@ -498,7 +537,22 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field </EuiFormRow> ); }, - paramEditor: function ParamEditor({ layer, updateLayer, currentColumn, columnId, indexPattern }) { + paramEditor: function ParamEditor({ + layer, + paramEditorUpdater, + currentColumn, + columnId, + indexPattern, + existingFields, + operationDefinitionMap, + ReferenceEditor, + paramEditorCustomProps, + ...rest + }) { + const [incompleteColumn, setIncompleteColumn] = useState<IncompleteColumn | undefined>( + undefined + ); + const hasRestrictions = indexPattern.hasRestrictions; const SEPARATOR = '$$$'; @@ -516,6 +570,9 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field if (value === 'rare') { return { type: 'rare', maxDocCount: DEFAULT_MAX_DOC_COUNT }; } + if (value === 'custom') { + return { type: 'custom' }; + } const parts = value.split(SEPARATOR); return { type: 'column', @@ -548,6 +605,12 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field }), }); } + orderOptions.push({ + value: toValue({ type: 'custom' }), + text: i18n.translate('xpack.lens.indexPattern.terms.orderCustomMetric', { + defaultMessage: 'Custom', + }), + }); const secondaryFieldsCount = currentColumn.params.secondaryFields ? currentColumn.params.secondaryFields.length @@ -559,7 +622,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field value={currentColumn.params.size} disabled={currentColumn.params.orderBy.type === 'rare'} onChange={(value) => { - updateLayer({ + paramEditorUpdater({ ...layer, columns: { ...layer.columns, @@ -590,7 +653,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field })} maxValue={MAXIMUM_MAX_DOC_COUNT} onChange={(value) => { - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -626,12 +689,13 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field > <EuiSelect compressed + fullWidth data-test-subj="indexPattern-terms-orderBy" options={orderOptions} value={toValue(currentColumn.params.orderBy)} onChange={(e: React.ChangeEvent<HTMLSelectElement>) => { const newOrderByValue = fromValue(e.target.value); - const updatedLayer = updateDefaultLabels( + let updatedLayer = updateDefaultLabels( updateColumnParam({ layer, columnId, @@ -640,8 +704,33 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field }), indexPattern ); - - updateLayer( + if (newOrderByValue.type === 'custom') { + const initialOperation = ( + operationDefinitionMap.count as OperationDefinition< + GenericIndexPatternColumn, + 'field' + > + ).buildColumn({ + layer, + indexPattern, + field: indexPattern.getFieldByName(DOCUMENT_FIELD_NAME)!, + }); + updatedLayer = updateColumnParam({ + layer: updatedLayer, + columnId, + paramName: 'orderAgg', + value: initialOperation, + }); + } else { + updatedLayer = updateColumnParam({ + layer: updatedLayer, + columnId, + paramName: 'orderAgg', + value: undefined, + }); + } + setIncompleteColumn(undefined); + paramEditorUpdater( updateColumnParam({ layer: updatedLayer, columnId, @@ -655,6 +744,113 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field })} /> </EuiFormRow> + {currentColumn.params.orderAgg && ReferenceEditor && ( + <> + <EuiSpacer size="s" /> + <ReferenceEditor + operationDefinitionMap={operationDefinitionMap} + functionLabel={i18n.translate('xpack.lens.indexPattern.terms.orderAgg.rankFunction', { + defaultMessage: 'Rank function', + })} + fieldLabel={i18n.translate('xpack.lens.indexPattern.terms.orderAgg.rankField', { + defaultMessage: 'Rank field', + })} + isInline={true} + paramEditorCustomProps={{ + ...paramEditorCustomProps, + isInline: true, + labels: getLabelForRankFunctions(currentColumn.params.orderAgg.operationType), + }} + layer={layer} + selectionStyle="full" + columnId={`${columnId}-orderAgg`} + currentIndexPattern={indexPattern} + paramEditorUpdater={(setter) => { + if (!isColumn(setter)) { + throw new Error('Setter should always be a column when ran here.'); + } + paramEditorUpdater( + updateColumnParam({ + layer, + columnId, + paramName: 'orderAgg', + value: setter, + }) + ); + }} + column={currentColumn.params.orderAgg} + incompleteColumn={incompleteColumn} + existingFields={existingFields} + onDeleteColumn={() => { + throw new Error('Should not be called'); + }} + onChooseField={(choice) => { + const field = choice.field && indexPattern.getFieldByName(choice.field); + if (field) { + const hypotethicalColumn = ( + operationDefinitionMap[choice.operationType] as OperationDefinition< + GenericIndexPatternColumn, + 'field' + > + ).buildColumn({ + previousColumn: currentColumn.params.orderAgg, + layer, + indexPattern, + field, + }); + setIncompleteColumn(undefined); + paramEditorUpdater( + updateColumnParam({ + layer, + columnId, + paramName: 'orderAgg', + value: hypotethicalColumn, + }) + ); + } else { + setIncompleteColumn({ + sourceField: choice.field, + operationType: choice.operationType, + }); + } + }} + onChooseFunction={(operationType: string, field?: IndexPatternField) => { + if (field) { + const hypotethicalColumn = ( + operationDefinitionMap[operationType] as OperationDefinition< + GenericIndexPatternColumn, + 'field' + > + ).buildColumn({ + previousColumn: currentColumn.params.orderAgg, + layer, + indexPattern, + field, + }); + setIncompleteColumn(undefined); + + paramEditorUpdater( + updateColumnParam({ + layer, + columnId, + paramName: 'orderAgg', + value: hypotethicalColumn, + }) + ); + } else { + setIncompleteColumn({ operationType }); + } + }} + validation={{ + input: ['field', 'managedReference'], + validateMetadata: (meta: OperationMetadata) => + meta.dataType === 'number' && !meta.isBucketed, + }} + {...rest} + /> + <EuiSpacer size="m" /> + </> + )} <EuiFormRow label={i18n.translate('xpack.lens.indexPattern.terms.orderDirection', { defaultMessage: 'Rank direction', @@ -698,7 +894,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field idPrefix, '' ) as TermsIndexPatternColumn['params']['orderDirection']; - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -729,7 +925,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field checked={Boolean(currentColumn.params.otherBucket)} disabled={currentColumn.params.orderBy.type === 'rare'} onChange={(e: EuiSwitchEvent) => - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -753,7 +949,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field data-test-subj="indexPattern-terms-missing-bucket" checked={Boolean(currentColumn.params.missingBucket)} onChange={(e: EuiSwitchEvent) => - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -791,7 +987,7 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field currentColumn.params.accuracyMode && currentColumn.params.orderBy.type !== 'rare' )} onChange={(e: EuiSwitchEvent) => - updateLayer( + paramEditorUpdater( updateColumnParam({ layer, columnId, @@ -808,3 +1004,21 @@ export const termsOperation: OperationDefinition<TermsIndexPatternColumn, 'field ); }, }; +function getLabelForRankFunctions(operationType: string) { + switch (operationType) { + case 'last_value': + return [ + i18n.translate('xpack.lens.indexPattern.terms.lastValue.sortRankBy', { + defaultMessage: 'Sort rank by', + }), + ]; + case 'percentile_rank': + return [ + i18n.translate('xpack.lens.indexPattern.terms.percentile.', { + defaultMessage: 'Percentile ranks', + }), + ]; + default: + return; + } +} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index 99c20bbd8bca6..33c8fcd1af665 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -22,12 +22,18 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { createMockedIndexPattern } from '../../../mocks'; import { ValuesInput } from './values_input'; import type { TermsIndexPatternColumn } from '.'; -import { GenericOperationDefinition, termsOperation, LastValueIndexPatternColumn } from '..'; +import { + GenericOperationDefinition, + termsOperation, + LastValueIndexPatternColumn, + operationDefinitionMap, +} from '..'; import { IndexPattern, IndexPatternLayer, IndexPatternPrivateState } from '../../../types'; import { FrameDatasourceAPI } from '../../../../types'; import { DateHistogramIndexPatternColumn } from '../date_histogram'; import { getOperationSupportMatrix } from '../../../dimension_panel/operation_support'; import { FieldSelect } from '../../../dimension_panel/field_select'; +import { ReferenceEditor } from '../../../dimension_panel/reference_editor'; // mocking random id generator function jest.mock('@elastic/eui', () => { @@ -65,11 +71,20 @@ const defaultProps = { http: {} as HttpSetup, indexPattern: createMockedIndexPattern(), // need to provide the terms operation as some helpers use operation specific features - operationDefinitionMap: { terms: termsOperation as unknown as GenericOperationDefinition }, + operationDefinitionMap, isFullscreen: false, toggleFullscreen: jest.fn(), setIsCloseable: jest.fn(), layerId: '1', + ReferenceEditor, + existingFields: { + 'my-fake-index-pattern': { + timestamp: true, + bytes: true, + memory: true, + source: true, + }, + }, }; describe('terms', () => { @@ -136,7 +151,8 @@ describe('terms', () => { {} as IndexPattern, layer, uiSettingsMock, - [] + [], + operationDefinitionMap ); expect(esAggsFn).toEqual( expect.objectContaining({ @@ -229,6 +245,59 @@ describe('terms', () => { ); }); + it('should pass orderAgg correctly', () => { + const termsColumn = layer.columns.col1 as TermsIndexPatternColumn; + const esAggsFn = termsOperation.toEsAggsFn( + { + ...termsColumn, + params: { + ...termsColumn.params, + orderAgg: { + label: 'Maximum of price', + dataType: 'number', + operationType: 'max', + sourceField: 'price', + isBucketed: false, + scale: 'ratio', + }, + orderBy: { + type: 'custom', + }, + }, + }, + 'col1', + {} as IndexPattern, + layer, + uiSettingsMock, + [], + operationDefinitionMap + ); + expect(esAggsFn).toEqual( + expect.objectContaining({ + arguments: expect.objectContaining({ + orderAgg: [ + { + chain: [ + { + arguments: { + enabled: [true], + field: ['price'], + id: ['col1-orderAgg'], + schema: ['metric'], + }, + function: 'aggMax', + type: 'function', + }, + ], + type: 'expression', + }, + ], + orderBy: ['custom'], + }), + }) + ); + }); + it('should default percentile rank with non integer value to alphabetical sort', () => { const newLayer = { ...layer, @@ -1801,7 +1870,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1824,7 +1893,7 @@ describe('terms', () => { ...createMockedIndexPattern(), hasRestrictions: true, }} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1839,7 +1908,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -1858,7 +1927,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={ { @@ -1885,7 +1954,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={{ ...(layer.columns.col1 as TermsIndexPatternColumn), @@ -1916,7 +1985,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={() => {}} + paramEditorUpdater={() => {}} columnId="col1" currentColumn={ { @@ -1965,7 +2034,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={{ ...(layer.columns.col1 as TermsIndexPatternColumn), @@ -1992,7 +2061,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={ { @@ -2020,7 +2089,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -2056,7 +2125,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -2070,6 +2139,7 @@ describe('terms', () => { 'column$$$col2', 'alphabetical', 'rare', + 'custom', ]); }); @@ -2079,7 +2149,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={ { ...layer.columns.col1, sourceField: 'memory' } as TermsIndexPatternColumn @@ -2094,6 +2164,7 @@ describe('terms', () => { expect(select.prop('options')!.map(({ value }) => value)).toEqual([ 'column$$$col2', 'alphabetical', + 'custom', ]); }); @@ -2103,7 +2174,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -2143,7 +2214,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -2160,7 +2231,7 @@ describe('terms', () => { <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -2183,13 +2254,210 @@ describe('terms', () => { }); }); + it('should render reference editor when order is set to custom metric', () => { + const updateLayerSpy = jest.fn(); + const currentLayer = { + ...layer, + columns: { + ...layer.columns, + col1: { + ...layer.columns.col1, + params: { + ...(layer.columns.col1 as TermsIndexPatternColumn).params, + type: 'custom', + orderDirection: 'desc', + orderAgg: { + label: 'Median of bytes', + dataType: 'number', + operationType: 'median', + isBucketed: false, + scale: 'ratio', + sourceField: 'bytes', + }, + }, + }, + }, + }; + const instance = shallow( + <InlineOptions + {...defaultProps} + layer={currentLayer} + paramEditorUpdater={updateLayerSpy} + columnId="col1" + currentColumn={currentLayer.columns.col1 as TermsIndexPatternColumn} + /> + ); + + expect(instance.find(`ReferenceEditor`)).toHaveLength(1); + + instance + .find(EuiSelect) + .find('[data-test-subj="indexPattern-terms-orderBy"]') + .simulate('change', { + target: { + value: 'column$$$col2', + }, + }); + + expect(updateLayerSpy).toHaveBeenCalledWith({ + ...currentLayer, + columns: { + ...currentLayer.columns, + col1: { + ...currentLayer.columns.col1, + params: { + ...(currentLayer.columns.col1 as TermsIndexPatternColumn).params, + orderAgg: undefined, + orderBy: { + columnId: 'col2', + type: 'column', + }, + }, + }, + }, + }); + }); + + it('should update column when changing the operation for orderAgg', () => { + const updateLayerSpy = jest.fn(); + const currentLayer = { + ...layer, + columns: { + ...layer.columns, + col1: { + ...layer.columns.col1, + params: { + ...(layer.columns.col1 as TermsIndexPatternColumn).params, + type: 'custom', + orderDirection: 'desc', + orderAgg: { + label: 'Median of bytes', + dataType: 'number', + operationType: 'median', + isBucketed: false, + scale: 'ratio', + sourceField: 'bytes', + }, + }, + }, + }, + }; + const instance = mount( + <InlineOptions + {...defaultProps} + layer={currentLayer} + paramEditorUpdater={updateLayerSpy} + columnId="col1" + currentColumn={currentLayer.columns.col1 as TermsIndexPatternColumn} + /> + ); + const refEditor = instance.find(`ReferenceEditor`); + expect(refEditor).toHaveLength(1); + + const functionComboBox = refEditor + .find(EuiComboBox) + .filter('[data-test-subj="indexPattern-reference-function"]'); + const option = functionComboBox.prop('options')!.find(({ label }) => label === 'Average')!; + + act(() => { + functionComboBox.prop('onChange')!([option]); + }); + + expect(updateLayerSpy).toHaveBeenCalledWith({ + ...currentLayer, + columns: { + ...currentLayer.columns, + col1: { + ...currentLayer.columns.col1, + params: { + ...(currentLayer.columns.col1 as TermsIndexPatternColumn).params, + orderAgg: expect.objectContaining({ + dataType: 'number', + isBucketed: false, + label: 'Average of bytes', + operationType: 'average', + sourceField: 'bytes', + }), + }, + }, + }, + }); + }); + + it('should update column when changing the field for orderAgg', () => { + const updateLayerSpy = jest.fn(); + const currentLayer = { + ...layer, + columns: { + ...layer.columns, + col1: { + ...layer.columns.col1, + params: { + ...(layer.columns.col1 as TermsIndexPatternColumn).params, + type: 'custom', + orderDirection: 'desc', + orderAgg: { + label: 'Median of bytes', + dataType: 'number', + operationType: 'median', + isBucketed: false, + scale: 'ratio', + sourceField: 'bytes', + }, + }, + }, + }, + }; + const instance = mount( + <InlineOptions + {...defaultProps} + layer={currentLayer} + paramEditorUpdater={updateLayerSpy} + columnId="col1" + currentColumn={currentLayer.columns.col1 as TermsIndexPatternColumn} + /> + ); + const refEditor = instance.find(`ReferenceEditor`); + expect(refEditor).toHaveLength(1); + + const comboBoxes = refEditor.find(EuiComboBox); + + const fieldComboBox = comboBoxes.filter('[data-test-subj="indexPattern-dimension-field"]'); + + const option = fieldComboBox + .prop('options')[0] + .options!.find(({ label }) => label === 'memory')!; + act(() => { + fieldComboBox.prop('onChange')!([option]); + }); + expect(updateLayerSpy).toHaveBeenCalledWith({ + ...currentLayer, + columns: { + ...currentLayer.columns, + col1: { + ...currentLayer.columns.col1, + params: { + ...(currentLayer.columns.col1 as TermsIndexPatternColumn).params, + orderAgg: expect.objectContaining({ + dataType: 'number', + isBucketed: false, + label: 'Median of memory', + operationType: 'median', + sourceField: 'memory', + }), + }, + }, + }, + }); + }); + it('should render current size value', () => { const updateLayerSpy = jest.fn(); const instance = mount( <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> @@ -2198,13 +2466,64 @@ describe('terms', () => { expect(instance.find(EuiFieldNumber).prop('value')).toEqual('3'); }); + it('should not update the column when the change creates incomplete column', () => { + const updateLayerSpy = jest.fn(); + const currentLayer = { + ...layer, + columns: { + ...layer.columns, + col1: { + ...layer.columns.col1, + params: { + ...(layer.columns.col1 as TermsIndexPatternColumn).params, + type: 'custom', + orderDirection: 'desc', + orderAgg: { + label: 'Count of records', + dataType: 'number', + operationType: 'count', + isBucketed: false, + scale: 'ratio', + sourceField: '___records___', + }, + }, + }, + }, + }; + const instance = mount( + <InlineOptions + {...defaultProps} + layer={currentLayer} + paramEditorUpdater={updateLayerSpy} + columnId="col1" + currentColumn={currentLayer.columns.col1 as TermsIndexPatternColumn} + /> + ); + const refEditor = instance.find(`ReferenceEditor`); + expect(refEditor).toHaveLength(1); + + const comboBoxes = refEditor.find(EuiComboBox); + + const functionComboBox = comboBoxes.filter( + '[data-test-subj="indexPattern-reference-function"]' + ); + const fieldComboBox = comboBoxes.filter('[data-test-subj="indexPattern-dimension-field"]'); + const option = functionComboBox.prop('options')!.find(({ label }) => label === 'Average')!; + act(() => { + functionComboBox.prop('onChange')!([option]); + }); + + expect(fieldComboBox.prop('isInvalid')).toBeTruthy(); + expect(updateLayerSpy).not.toHaveBeenCalled(); + }); + it('should update state with the size value', () => { const updateLayerSpy = jest.fn(); const instance = mount( <InlineOptions {...defaultProps} layer={layer} - updateLayer={updateLayerSpy} + paramEditorUpdater={updateLayerSpy} columnId="col1" currentColumn={layer.columns.col1 as TermsIndexPatternColumn} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/types.ts index a292faabec742..35a9c7e02147c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/types.ts @@ -18,7 +18,9 @@ export interface TermsIndexPatternColumn extends FieldBasedIndexPatternColumn { orderBy: | { type: 'alphabetical'; fallback?: boolean } | { type: 'rare'; maxDocCount: number } - | { type: 'column'; columnId: string }; + | { type: 'column'; columnId: string } + | { type: 'custom' }; + orderAgg?: FieldBasedIndexPatternColumn; orderDirection: 'asc' | 'desc'; otherBucket?: boolean; missingBucket?: boolean; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx index 3130f2b8a5265..b244bdd54aad3 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/values_input.tsx @@ -77,6 +77,7 @@ export const ValuesInput = ({ } > <EuiFieldNumber + fullWidth min={minValue} max={maxValue} step={1} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index ab9319957afca..c89ec6ae02199 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -41,6 +41,9 @@ import { CoreStart } from '@kbn/core/public'; jest.mock('.'); jest.mock('../../id_generator'); +jest.mock('../dimension_panel/reference_editor', () => ({ + ReferenceEditor: () => null, +})); const indexPatternFields = [ { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 434370943fbc1..a1edd6132d22a 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -503,17 +503,14 @@ export function replaceColumn({ tempLayer = { ...tempLayer, + columnOrder: getColumnOrder(tempLayer), columns: { ...tempLayer.columns, [columnId]: column, }, }; return updateDefaultLabels( - { - ...tempLayer, - columnOrder: getColumnOrder(tempLayer), - columns: adjustColumnReferencesForChangedColumn(tempLayer, columnId), - }, + adjustColumnReferencesForChangedColumn(tempLayer, columnId), indexPattern ); } else if ( @@ -573,11 +570,14 @@ export function replaceColumn({ } return updateDefaultLabels( - { - ...tempLayer, - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), - }, + adjustColumnReferencesForChangedColumn( + { + ...tempLayer, + columnOrder: getColumnOrder(newLayer), + columns: newLayer.columns, + }, + columnId + ), indexPattern ); } @@ -592,13 +592,18 @@ export function replaceColumn({ indexPattern ); - const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; + const newLayer = { + ...tempLayer, + columns: { ...tempLayer.columns, [columnId]: newColumn }, + }; return updateDefaultLabels( - { - ...tempLayer, - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), - }, + adjustColumnReferencesForChangedColumn( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + }, + columnId + ), indexPattern ); } @@ -650,11 +655,13 @@ export function replaceColumn({ } const newLayer = { ...tempLayer, columns: { ...tempLayer.columns, [columnId]: newColumn } }; return updateDefaultLabels( - { - ...tempLayer, - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), - }, + adjustColumnReferencesForChangedColumn( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + }, + columnId + ), indexPattern ); } else if ( @@ -677,11 +684,13 @@ export function replaceColumn({ { ...layer, columns: { ...layer.columns, [columnId]: newColumn } }, columnId ); - return { - ...newLayer, - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, columnId), - }; + return adjustColumnReferencesForChangedColumn( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + }, + columnId + ); } else { throw new Error('nothing changed'); } @@ -836,11 +845,13 @@ function applyReferenceTransition({ }, }, }; - layer = { - ...layer, - columnOrder: getColumnOrder(newLayer), - columns: adjustColumnReferencesForChangedColumn(newLayer, newId), - }; + layer = adjustColumnReferencesForChangedColumn( + { + ...newLayer, + columnOrder: getColumnOrder(newLayer), + }, + newId + ); return newId; } @@ -977,11 +988,13 @@ function applyReferenceTransition({ }, }; return updateDefaultLabels( - { - ...layer, - columnOrder: getColumnOrder(layer), - columns: adjustColumnReferencesForChangedColumn(layer, columnId), - }, + adjustColumnReferencesForChangedColumn( + { + ...layer, + columnOrder: getColumnOrder(layer), + }, + columnId + ), indexPattern ); } @@ -1044,11 +1057,13 @@ function addBucket( columns: { ...layer.columns, [addedColumnId]: column }, columnOrder: updatedColumnOrder, }; - return { - ...tempLayer, - columns: adjustColumnReferencesForChangedColumn(tempLayer, addedColumnId), - columnOrder: getColumnOrder(tempLayer), - }; + return adjustColumnReferencesForChangedColumn( + { + ...tempLayer, + columnOrder: getColumnOrder(tempLayer), + }, + addedColumnId + ); } export function reorderByGroups( @@ -1108,11 +1123,13 @@ function addMetric( [addedColumnId]: column, }, }; - return { - ...tempLayer, - columnOrder: getColumnOrder(tempLayer), - columns: adjustColumnReferencesForChangedColumn(tempLayer, addedColumnId), - }; + return adjustColumnReferencesForChangedColumn( + { + ...tempLayer, + columnOrder: getColumnOrder(tempLayer), + }, + addedColumnId + ); } export function getMetricOperationTypes(field: IndexPatternField) { @@ -1146,7 +1163,7 @@ export function updateColumnLabel<C extends GenericIndexPatternColumn>({ }; } -export function updateColumnParam<C extends GenericIndexPatternColumn>({ +export function updateColumnParam({ layer, columnId, paramName, @@ -1157,15 +1174,15 @@ export function updateColumnParam<C extends GenericIndexPatternColumn>({ paramName: string; value: unknown; }): IndexPatternLayer { - const oldColumn = layer.columns[columnId]; + const currentColumn = layer.columns[columnId]; return { ...layer, columns: { ...layer.columns, [columnId]: { - ...oldColumn, + ...currentColumn, params: { - ...('params' in oldColumn ? oldColumn.params : {}), + ...('params' in currentColumn ? currentColumn.params : {}), [paramName]: value, }, }, @@ -1210,7 +1227,10 @@ export function adjustColumnReferencesForChangedColumn( : currentColumn; } }); - return newColumns; + return { + ...layer, + columns: newColumns, + }; } export function deleteColumn({ @@ -1238,13 +1258,13 @@ export function deleteColumn({ const hypotheticalColumns = { ...layer.columns }; delete hypotheticalColumns[columnId]; - let newLayer = { - ...layer, - columns: adjustColumnReferencesForChangedColumn( - { ...layer, columns: hypotheticalColumns }, - columnId - ), - }; + let newLayer = adjustColumnReferencesForChangedColumn( + { + ...layer, + columns: hypotheticalColumns, + }, + columnId + ); extraDeletions.forEach((id) => { newLayer = deleteColumn({ layer: newLayer, columnId: id, indexPattern }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts index 82093b31a09d9..396bf78f82db6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/operations.ts @@ -39,7 +39,7 @@ export function getOperations(): OperationType[] { /** * Returns a list of the display names of all operations with any guaranteed order. */ -export function getOperationDisplay() { +export const getOperationDisplay = memoize(() => { const display = {} as Record< OperationType, { @@ -54,7 +54,7 @@ export function getOperationDisplay() { }; }); return display; -} +}); export function getSortScoreByPriority( a: GenericOperationDefinition, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index 6cfab965f36a9..8e59f299c6917 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -130,7 +130,8 @@ function getExpressionForLayer( indexPattern, layer, uiSettings, - orderedColumnIds + orderedColumnIds, + operationDefinitionMap ); if (wrapInFilter) { aggAst = buildExpressionFunction<AggFunctionsMapping['aggFilteredMetric']>( diff --git a/x-pack/plugins/lens/public/shared_components/collapse_setting.tsx b/x-pack/plugins/lens/public/shared_components/collapse_setting.tsx index 936179c993d4c..f5558850d6af0 100644 --- a/x-pack/plugins/lens/public/shared_components/collapse_setting.tsx +++ b/x-pack/plugins/lens/public/shared_components/collapse_setting.tsx @@ -46,6 +46,7 @@ export function CollapseSetting({ fullWidth > <EuiSelect + fullWidth compressed data-test-subj="indexPattern-terms-orderBy" options={options} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap b/x-pack/plugins/lens/public/shared_components/field_picker/__snapshots__/lens_field_icon.test.tsx.snap similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/__snapshots__/lens_field_icon.test.tsx.snap rename to x-pack/plugins/lens/public/shared_components/field_picker/__snapshots__/lens_field_icon.test.tsx.snap diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss new file mode 100644 index 0000000000000..d8e4e9b8f7207 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.scss @@ -0,0 +1,7 @@ +.lnFieldPicker__option--incompatible { + color: $euiColorLightShade; +} + +.lnFieldPicker__option--nonExistant { + background-color: $euiColorLightestShade; +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx new file mode 100644 index 0000000000000..bde920446d468 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/field_picker.tsx @@ -0,0 +1,136 @@ +/* + * 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 './field_picker.scss'; +import React, { useRef } from 'react'; +import { i18n } from '@kbn/i18n'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { EuiComboBox, EuiComboBoxProps, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import classNames from 'classnames'; +import { DataType } from '../../types'; +import { LensFieldIcon } from './lens_field_icon'; +import { TruncatedLabel } from './truncated_label'; +import type { FieldOptionValue, FieldOption } from './types'; + +export interface FieldPickerProps<T extends FieldOptionValue> + extends EuiComboBoxProps<FieldOption<T>['value']> { + options: Array<FieldOption<T>>; + selectedField?: string; + onChoose: (choice: T | undefined) => void; + onDelete?: () => void; + fieldIsInvalid: boolean; + 'data-test-subj'?: string; +} + +const DEFAULT_COMBOBOX_WIDTH = 305; +const COMBOBOX_PADDINGS = 90; +const DEFAULT_FONT = '14px Inter'; + +export function FieldPicker<T extends FieldOptionValue>({ + selectedOptions, + options, + onChoose, + onDelete, + fieldIsInvalid, + ['data-test-subj']: dataTestSub, + ...rest +}: FieldPickerProps<T>) { + const styledOptions = options?.map(({ compatible, exists, ...otherAttr }) => { + if (otherAttr.options) { + return { + ...otherAttr, + compatible, + exists, + options: otherAttr.options.map((fieldOption) => ({ + ...fieldOption, + className: classNames({ + 'lnFieldPicker__option--incompatible': !fieldOption.compatible, + 'lnFieldPicker__option--nonExistant': !fieldOption.exists, + }), + })), + }; + } + return { + ...otherAttr, + compatible, + exists, + className: classNames({ + 'lnFieldPicker__option--incompatible': !compatible, + 'lnFieldPicker__option--nonExistant': !exists, + }), + }; + }); + const comboBoxRef = useRef<HTMLInputElement>(null); + const [labelProps, setLabelProps] = React.useState<{ + width: number; + font: string; + }>({ + width: DEFAULT_COMBOBOX_WIDTH - COMBOBOX_PADDINGS, + font: DEFAULT_FONT, + }); + + const computeStyles = (_e: UIEvent | undefined, shouldRecomputeAll = false) => { + if (comboBoxRef.current) { + const current = { + ...labelProps, + width: comboBoxRef.current?.clientWidth - COMBOBOX_PADDINGS, + }; + if (shouldRecomputeAll) { + current.font = window.getComputedStyle(comboBoxRef.current).font; + } + setLabelProps(current); + } + }; + + useEffectOnce(() => { + if (comboBoxRef.current) { + computeStyles(undefined, true); + } + window.addEventListener('resize', computeStyles); + }); + + return ( + <div ref={comboBoxRef}> + <EuiComboBox + fullWidth + compressed + isClearable={false} + data-test-subj={dataTestSub ?? 'indexPattern-dimension-field'} + placeholder={i18n.translate('xpack.lens.fieldPicker.fieldPlaceholder', { + defaultMessage: 'Field', + })} + options={styledOptions} + isInvalid={fieldIsInvalid} + selectedOptions={selectedOptions} + singleSelection={{ asPlainText: true }} + onChange={(choices) => { + if (choices.length === 0) { + onDelete?.(); + return; + } + onChoose(choices[0].value); + }} + renderOption={(option, searchValue) => { + return ( + <EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}> + <EuiFlexItem grow={null}> + <LensFieldIcon + type={(option.value as unknown as { dataType: DataType }).dataType} + fill="none" + /> + </EuiFlexItem> + <EuiFlexItem> + <TruncatedLabel {...labelProps} label={option.label} search={searchValue} /> + </EuiFlexItem> + </EuiFlexGroup> + ); + }} + {...rest} + /> + </div> + ); +} diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/index.ts b/x-pack/plugins/lens/public/shared_components/field_picker/index.ts new file mode 100644 index 0000000000000..d9e18182dd0d8 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/index.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. + */ + +export { LensFieldIcon } from './lens_field_icon'; +export { FieldPicker } from './field_picker'; +export { TruncatedLabel } from './truncated_label'; +export type { FieldOptionValue, FieldOption } from './types'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.test.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.test.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.tsx similarity index 82% rename from x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.tsx index be30712a9de01..fabb8cab6fd0e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/lens_field_icon.tsx +++ b/x-pack/plugins/lens/public/shared_components/field_picker/lens_field_icon.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { FieldIcon, FieldIconProps } from '@kbn/react-field'; -import { DataType } from '../types'; -import { normalizeOperationDataType } from './pure_utils'; +import { DataType } from '../../types'; +import { normalizeOperationDataType } from '../../indexpattern_datasource/pure_utils'; export function LensFieldIcon({ type, ...rest }: FieldIconProps & { type: DataType }) { return ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.test.tsx diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx b/x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.tsx similarity index 100% rename from x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx rename to x-pack/plugins/lens/public/shared_components/field_picker/truncated_label.tsx diff --git a/x-pack/plugins/lens/public/shared_components/field_picker/types.ts b/x-pack/plugins/lens/public/shared_components/field_picker/types.ts new file mode 100644 index 0000000000000..4d607b47ef914 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/field_picker/types.ts @@ -0,0 +1,26 @@ +/* + * 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 { EuiComboBoxOptionOption } from '@elastic/eui'; +import type { DataType } from '../../types'; + +export interface FieldOptionValue { + type: 'field'; + field: string; + dataType?: DataType; +} + +interface FieldValue<T> { + label: string; + value: T; + exists: boolean; + compatible: number | boolean; + 'data-test-subj'?: string; + // defined in groups + options?: Array<FieldValue<T>>; +} + +export type FieldOption<T extends FieldOptionValue> = FieldValue<T> & EuiComboBoxOptionOption<T>; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index 7da63a6947211..e3a9af00ad005 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -9,6 +9,8 @@ export type { ToolbarPopoverProps } from './toolbar_popover'; export { ToolbarPopover } from './toolbar_popover'; export { LegendSettingsPopover } from './legend_settings_popover'; export { PalettePicker } from './palette_picker'; +export { FieldPicker, LensFieldIcon, TruncatedLabel } from './field_picker'; +export type { FieldOption, FieldOptionValue } from './field_picker'; export { RangeInputField } from './range_input_field'; export { BucketAxisBoundsControl, diff --git a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx index a048439856ac5..efd1caba7e4da 100644 --- a/x-pack/plugins/lens/public/shared_components/palette_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/palette_picker.tsx @@ -44,6 +44,7 @@ export function PalettePicker({ > <> <EuiColorPalettePicker + fullWidth data-test-subj="lns-palettePicker" compressed palettes={palettesToShow} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 770f4bee7eecd..2305e7215ccfc 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -430,7 +430,10 @@ export type DatasourceDimensionProps<T> = SharedDimensionProps & { invalid?: boolean; invalidMessage?: string; }; -export type ParamEditorCustomProps = Record<string, unknown> & { label?: string }; +export type ParamEditorCustomProps = Record<string, unknown> & { + labels?: string[]; + isInline?: boolean; +}; // The only way a visualization has to restrict the query building export type DatasourceDimensionEditorProps<T = unknown> = DatasourceDimensionProps<T> & { // Not a StateSetter because we have this unique use case of determining valid columns 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 193a91523ecc8..42717a3894303 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 @@ -458,9 +458,11 @@ export const getReferenceConfiguration = ({ enableDimensionEditor: true, supportStaticValue: true, paramEditorCustomProps: { - label: i18n.translate('xpack.lens.indexPattern.staticValue.label', { - defaultMessage: 'Reference line value', - }), + labels: [ + i18n.translate('xpack.lens.indexPattern.staticValue.label', { + defaultMessage: 'Reference line value', + }), + ], }, supportFieldFormat: false, dataTestSubj, diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index 84cea6feead06..340931efdcd52 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -1,12 +1,118 @@ -# Lens +Lens is a visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. -Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. - -## Embedding +# Lens Embedding It's possible to embed Lens visualizations in other apps using `EmbeddableComponent` and `navigateToPrefilledEditor` exposed via contract. For more information check out the example in `x-pack/examples/embedded_lens_example`. +### Embedding guidance + +When adding visualizations to a solution page, there are multiple ways to approach this with pros and cons: + +* #### **Use a dashboard** + If the app page you are planning to build strongly resembles a regular dashboard, it might not even be necessary to write code - configuring a dashboard might be a better choice. The Presentation team is currently working on making it possible to embed dashboard into the solution navigation, which allows you to offer visualization and filter functionality without writing custom code. If possible this option should be chosen because of the low maintenance and development effort as well as the high flexibility for a user to clone the preset dashboard and start customizing it in various ways. + + Pros: + * No need to write and maintain custom code + * "Open in Lens" comes for free + * Ability for the user to customize/add/remove dashboard panels comes for free + + Cons: + * Limited data processing/visualization options - if the dashboard doesn't support it, it can't be used +* #### **Use Lens embeddable** + Using the Lens embeddable is an easy way to handle the rendering of charts. It allows you to specify data fetching and presentational properties of the final chart in a declarative way (the "Lens attributes") - everything is handled within the component (including re-fetching data on changing inputs). By using the `navigateToPrefilledEditor` method which takes the same configuration as the embeddable component, adding an "Open in Lens editor" button to your application comes at almost no additional cost. Such a button is always recommended as it allows a user to drill down further and explore the data on their own, using the current chart as a starting point. This approach is already widely deployed and should be the default choice for new visualizations. + + Pros: + * No need to manage searches and rendering logic on your own + * "Open in Lens" comes for free + + Cons: + * Each panel does its own data fetching and rendering (can lead to performance problems for high number of embeddables on a single page, e.g. more than 20) + * Limited data processing options - if the Lens UI doesn't support it, it can't be used + * Limited visualization options - if Lens can't do it, it's not possible +* #### **Using custom data fetching and rendering** + In case the disadvantages of using the Lens embeddable heavily affect your use case, it sometimes makes sense to roll your own data fetching and rendering by using the underlying APIs of search service and `elastic-charts` directly. This allows a high degree of flexibility when it comes to data processing, efficiently querying data for multiple charts in a single query and adjusting small details in how charts are rendered. However, do not choose these option lightly as maintenance as well as initial development effort will most likely be much higher than by using the Lens embeddable directly. In this case, almost always an "Open in Lens" button can still be offered to the user to drill down and further explore the data by generating a Lens configuration which is similar to the displayed visualization given the possibilities of Lens. Keep in mind that for the "Open in Lens" flow, the most important property isn't perfect fidelity of the chart but retaining the mental context of the user when switching so they don't have to start over. It's also possible to mix this approach with Lens embeddables on a single page. **Note**: In this situation, please let the VisEditors team know what features you are missing / why you chose not to use Lens. + + Pros: + * Full flexibility in data fetching optimization and chart rendering + + Cons: + * "Open in Lens" requires additional logic + * High maintenance and development effort + +## Getting started + +The `EmbeddableComponent` react component is exposed on the Lens plugin contract. In order to use it, +* Make sure you have a data view created for the data you plan to work with +* Add `lens` to `requiredPlugins` in your plugins `kibana.json` +* In the mount callback of your app, get `lens.EmbeddableComponent` from the start contract and pass it into your apps react tree +* In the place where you want to render a visualization, add the component to the tree: +```tsx +<div> + // my app + <EmbeddableComponent + id="" + style={{ height: 500 }} + timeRange={{ from: 'now - 15m', to: 'now' }} + attributes={attributes} + /> +</div> +``` + +You can see a working example of this in the `x-pack/examples/embedded_lens_example` directory. + +The `attributes` variable contains the configuration for the Lens visualization. The details are explained in the section below. It's difficult to set up this object manually, in order to quickly get to a functioning starting point, start your Kibana server with example plugins via +``` +yarn start --run-examples +``` + +This will add an `Open in Playground` action to the menu bar in the Lens editor. With this option, try to configure the chart configuration directly in the editor, then open it in the playground to see the attributes object to copy. This works for any possible Lens visualization. + +![Go to playground](./to_playground.gif "Go to playground") + +## Lens attributes explained + +The Lens attributes object contains multiple sections concerned with different aspects of the visualizations. + +On a high level there are references, datasource state, visualization state and filters: + +### References + +References (`references`) are regular saved object references forming a graph of saved objects which depend on each other. For the Lens case, these references are always data views (called `type: "index-pattern"`) in code, referencing data views which are used in the current Lens visualization. Often there is just a single data view in use, but it's possible to use multiple data views for multiple layers in a Lens xy chart. The `id` of a reference needs to be the saved object id of the referenced data view (see the "Handling data views" section below). The `name` of the reference is comprised out of multiple parts used to map the data view to the correct layer : `indexpattern-datasource-layer-<id of the layer>`. Even if multiple layers are using the same data view, there has to be one reference per layer (all pointing to the same data view id). + +### Datasource state + +The data source state (`state.datasourceStates.indexPattern.layers`) contains the configuration state of the data fetching and processing part of Lens. It's not specific to a certain representation (xy, pie, gauge, ...), but instead it defines a data table per layer made out of columns with various properties. This data table is passed over to the visualization state which maps it to various dimensions of the specific visualization. Layer and columns have unique ids which are shared amongst visualization and datasource - it's important to make sure they are always in sync. The keys of the `state.datasourceStates.indexPattern.layers` object are the layer ids. Lens editor chooses uuids for these, but when programmatically generating Lens attributes, any string can be used for them. The `layers[<layer id>].columns` object is constructed in a similar way (keys represent the column ids). The `operationType` property defines the type of the column, other properties depend on the specific operation. Types for individual parts of the datasource state are provided (check the `lens/public` export, e.g. there's the `MaxIndexPatternColumn` for a column of operation type `max`) + +### Visualization state + +The visualization state (`state.visualization`) depends on the chosen visualization type (`visualizationType`). Layer ids and accessor properties in this state have to correspond to the layer ids and column ids of the datasource state. Types for individual visualizations are exported as standalone interfaces (e.g. `XYState` or `HeatmapVisualizationState`). + +### Filters + +Filters and query `state.filters`/`state.query` define the visualization-global filters and query applied to all layers of the visualization. The query is rendered in the top level search bar in the editor while filters are rendered as filter pills. Filters and query state defined this way is used for dashboards and Discover in the same way. + +### Callbacks + +The `EmbeddableComponent` also takes a set of callbacks to react to user interactions with the embedded Lens visualization to integrate the visualization with the surrounding app: `onLoad`, `onBrushEnd`, `onFilter`, `onTableRowClick`. A common pattern is to keep state in the solution app which is updated within these callbacks - re-rendering the surrounding application will change the Lens attributes passed to the component which will re-render the visualization (including re-fetching data if necessary). + +## Handling data views + +Currently it's necessary to have a data view saved object to use the Lens embeddable. Use the data view service to find an existing data view for a given index pattern or create a new one if it doesn't exist yet: +```ts +let dataView = (await dataViews.find('my-pattern-*', 1))[0]; +if (!dataView) { + dataView = await dataViews.createAndSave({ + title: 'my-pattern-*', + timeFieldName: '@timestamp' + }); +} +const dataViewIdForLens = dataView.id; +``` + +# Lens Development + +The following sections are concerned with developing the Lens plugin itself. ## Testing Run all tests from the `x-pack` root directory diff --git a/x-pack/plugins/lens/to_playground.gif b/x-pack/plugins/lens/to_playground.gif new file mode 100644 index 0000000000000..56ce6f3975e53 Binary files /dev/null and b/x-pack/plugins/lens/to_playground.gif differ diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx index a154bf97ef56b..ff0cfd21fb82a 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/test_models/models/text_input.tsx @@ -8,7 +8,7 @@ import React, { FC, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import useObservable from 'react-use/lib/useObservable'; -import { EuiTextArea } from '@elastic/eui'; +import { EuiTextArea, EuiFormRow } from '@elastic/eui'; import { RUNNING_STATE } from './inference_base'; import type { InferrerType } from '.'; @@ -25,20 +25,26 @@ export const TextInput: FC<{ const runningState = useObservable(inferrer.runningState$); return ( - <EuiTextArea - placeholder={ - placeholder ?? - i18n.translate('xpack.ml.trainedModels.testModelsFlyout.generalTextInput.inputText', { - defaultMessage: 'Input text', - }) - } - value={inputText} - disabled={runningState === RUNNING_STATE.RUNNING} - fullWidth - onChange={(e) => { - setInputText(e.target.value); - }} - /> + <EuiFormRow + label={i18n.translate('xpack.ml.trainedModels.testModelsFlyout.generalTextInput.inputTitle', { + defaultMessage: 'Input text', + })} + > + <EuiTextArea + placeholder={ + placeholder ?? + i18n.translate('xpack.ml.trainedModels.testModelsFlyout.generalTextInput.inputText', { + defaultMessage: 'Input text', + }) + } + value={inputText} + disabled={runningState === RUNNING_STATE.RUNNING} + fullWidth + onChange={(e) => { + setInputText(e.target.value); + }} + /> + </EuiFormRow> ); }; diff --git a/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts b/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts index 4d18eb3cd0b2f..cd65988e76faa 100644 --- a/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts +++ b/x-pack/plugins/observability/e2e/journeys/exploratory_view.ts @@ -44,7 +44,7 @@ journey('Exploratory view', async ({ page, params }) => { }); step('renders as expected', async () => { - await page.waitForTimeout(60 * 1000); + await Promise.all([page.waitForNavigation(TIMEOUT_60_SEC), page.click('text=Explore data')]); await page.click('text=User experience (RUM)'); await page.click('[aria-label="Toggle series information"] >> text=Page views', TIMEOUT_60_SEC); await page.click('[aria-label="Edit series"]', TIMEOUT_60_SEC); diff --git a/x-pack/plugins/observability/e2e/journeys/index.ts b/x-pack/plugins/observability/e2e/journeys/index.ts index 4eb9761d18482..0119d0c4b9ed1 100644 --- a/x-pack/plugins/observability/e2e/journeys/index.ts +++ b/x-pack/plugins/observability/e2e/journeys/index.ts @@ -6,3 +6,4 @@ */ export * from './exploratory_view'; +export * from './step_duration.journey'; diff --git a/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.ts new file mode 100644 index 0000000000000..8618286ae4985 --- /dev/null +++ b/x-pack/plugins/observability/e2e/journeys/step_duration.journey.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 { journey, step, before } from '@elastic/synthetics'; +import { createExploratoryViewUrl } from '../../public/components/shared/exploratory_view/configurations/exploratory_view_url'; +import { loginToKibana, TIMEOUT_60_SEC, waitForLoadingToFinish } from '../utils'; + +journey('Exploratory view', async ({ page, params }) => { + before(async () => { + await waitForLoadingToFinish({ page }); + }); + + const expUrl = createExploratoryViewUrl({ + reportType: 'kpi-over-time', + allSeries: [ + { + dataType: 'synthetics', + time: { + from: 'now-10y', + to: 'now', + }, + name: 'synthetics-series-1', + breakdown: 'monitor.type', + selectedMetricField: 'monitor.duration.us', + reportDefinitions: { + 'url.full': ['ALL_VALUES'], + }, + }, + ], + }); + + const baseUrl = `${params.kibanaUrl}${expUrl}`; + + step('Go to Exploratory view', async () => { + await page.goto(baseUrl, { + waitUntil: 'networkidle', + }); + await loginToKibana({ + page, + user: { username: 'elastic', password: 'changeme' }, + dismissTour: false, + }); + }); + + step('Open exploratory view with monitor duration', async () => { + await Promise.all([ + page.waitForNavigation(TIMEOUT_60_SEC), + page.click('text=Explore data', TIMEOUT_60_SEC), + ]); + + await waitForLoadingToFinish({ page }); + await page.click('text=browser', TIMEOUT_60_SEC); + await page.click('text=http'); + await page.click('[aria-label="Remove report metric"]'); + await page.click('button:has-text("Select report metric")'); + await page.click('button:has-text("Step duration")'); + await page.click('text=Select an option: Monitor type, is selectedMonitor type >> button'); + await page.click('button[role="option"]:has-text("Step name")'); + await page.click('.euiComboBox__inputWrap'); + await page.click( + 'text=Search Monitor nameCombo box. Selected. Combo box input. Search Monitor name. Ty' + ); + await page.click('button[role="option"]:has-text("test-monitor - inline")'); + await page.click('button:has-text("Apply changes")'); + + await waitForLoadingToFinish({ page }); + + await page.click('[aria-label="series color: #54b399"]'); + await page.click('[aria-label="series color: #6092c0"]'); + await page.click('[aria-label="series color: #d36086"] path'); + await page.click('[aria-label="series color: #9170b8"]'); + await page.click('[aria-label="series color: #ca8eae"]'); + await page.click('[aria-label="series color: #d6bf57"]'); + await page.click('text=load homepage'); + await page.click('text=load homepage'); + await page.click('text=load github'); + await page.click('text=load github'); + await page.click('text=load google'); + await page.click('text=load google'); + await page.click('text=hover over products menu'); + await page.click('text=hover over products menu'); + await page.click('text=load homepage 1'); + await page.click('text=load homepage 1'); + await page.click('text=load homepage 2'); + await page.click('text=load homepage 2'); + }); +}); diff --git a/x-pack/plugins/observability/e2e/synthetics_run.ts b/x-pack/plugins/observability/e2e/synthetics_run.ts index f6ac3aaa49fda..f873c898aa45d 100644 --- a/x-pack/plugins/observability/e2e/synthetics_run.ts +++ b/x-pack/plugins/observability/e2e/synthetics_run.ts @@ -25,9 +25,14 @@ async function runE2ETests({ readConfigFile }: FtrConfigProviderContext) { await syntheticsRunner.setup(); - const fixturesDir = path.join(__dirname, '../../ux/e2e/fixtures/'); - - await syntheticsRunner.loadTestData(fixturesDir, ['rum_8.0.0', 'rum_test_data']); + await syntheticsRunner.loadTestData(path.join(__dirname, '../../ux/e2e/fixtures/'), [ + 'rum_8.0.0', + 'rum_test_data', + ]); + await syntheticsRunner.loadTestData( + path.join(__dirname, '../../synthetics/e2e/fixtures/es_archiver/'), + ['full_heartbeat', 'browser'] + ); await syntheticsRunner.loadTestFiles(async () => { require('./journeys'); }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index fab9681bcf0c3..daf03d8fb33ef 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -584,6 +584,7 @@ export class LensAttributes { getFieldMeta(sourceField: string, layerConfig: LayerConfig) { if (sourceField === REPORT_METRIC_FIELD) { const { + palette, fieldName, columnType, columnLabel, @@ -594,6 +595,7 @@ export class LensAttributes { } = parseCustomFieldName(layerConfig.seriesConfig, layerConfig.selectedMetricField); const fieldMeta = layerConfig.indexPattern.getFieldByName(fieldName!); return { + palette, fieldMeta, fieldName, columnType, @@ -891,40 +893,53 @@ export class LensAttributes { } getDataLayers(): XYState['layers'] { - const dataLayers = this.layerConfigs.map((layerConfig, index) => ({ - accessors: [ - `y-axis-column-layer${index}`, - ...Object.keys(this.getChildYAxises(layerConfig, `layer${index}`, undefined, true)), - ], - layerId: `layer${index}`, - layerType: 'data' as any, - seriesType: layerConfig.seriesType || layerConfig.seriesConfig.defaultSeriesType, - palette: layerConfig.seriesConfig.palette, - yConfig: layerConfig.seriesConfig.yConfig || [ - { - forAccessor: `y-axis-column-layer${index}`, - color: layerConfig.color, - /* if the fields format matches the field format of the first layer, use the default y axis (right) - * if not, use the secondary y axis (left) */ - axisMode: - layerConfig.indexPattern.fieldFormatMap[layerConfig.selectedMetricField]?.id === - this.layerConfigs[0].indexPattern.fieldFormatMap[ - this.layerConfigs[0].selectedMetricField - ]?.id - ? ('left' as YAxisMode) - : ('right' as YAxisMode), - }, - ], - xAccessor: `x-axis-column-layer${index}`, - ...(layerConfig.breakdown && - layerConfig.breakdown !== PERCENTILE && - layerConfig.seriesConfig.xAxisColumn.sourceField !== USE_BREAK_DOWN_COLUMN - ? { splitAccessor: `breakdown-column-layer${index}` } - : {}), - ...(this.layerConfigs[0].seriesConfig.yTitle - ? { yTitle: this.layerConfigs[0].seriesConfig.yTitle } - : {}), - })); + const dataLayers = this.layerConfigs.map((layerConfig, index) => { + const { sourceField } = layerConfig.seriesConfig.yAxisColumns[0]; + + let palette = layerConfig.seriesConfig.palette; + + if (sourceField) { + const fieldMeta = this.getFieldMeta(sourceField, layerConfig); + if (fieldMeta.palette) { + palette = fieldMeta.palette; + } + } + + return { + accessors: [ + `y-axis-column-layer${index}`, + ...Object.keys(this.getChildYAxises(layerConfig, `layer${index}`, undefined, true)), + ], + layerId: `layer${index}`, + layerType: 'data' as any, + seriesType: layerConfig.seriesType || layerConfig.seriesConfig.defaultSeriesType, + palette: palette ?? layerConfig.seriesConfig.palette, + yConfig: layerConfig.seriesConfig.yConfig || [ + { + forAccessor: `y-axis-column-layer${index}`, + color: layerConfig.color, + /* if the fields format matches the field format of the first layer, use the default y axis (right) + * if not, use the secondary y axis (left) */ + axisMode: + layerConfig.indexPattern.fieldFormatMap[layerConfig.selectedMetricField]?.id === + this.layerConfigs[0].indexPattern.fieldFormatMap[ + this.layerConfigs[0].selectedMetricField + ]?.id + ? ('left' as YAxisMode) + : ('right' as YAxisMode), + }, + ], + xAccessor: `x-axis-column-layer${index}`, + ...(layerConfig.breakdown && + layerConfig.breakdown !== PERCENTILE && + layerConfig.seriesConfig.xAxisColumn.sourceField !== USE_BREAK_DOWN_COLUMN + ? { splitAccessor: `breakdown-column-layer${index}` } + : {}), + ...(this.layerConfigs[0].seriesConfig.yTitle + ? { yTitle: this.layerConfigs[0].seriesConfig.yTitle } + : {}), + }; + }); const referenceLineLayers: XYState['layers'] = []; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts index c626ba5d522c2..aff40f980f25f 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts @@ -75,7 +75,6 @@ export function getSyntheticsKPIConfig({ dataView }: ConfigProps): SeriesConfig PERCENTILE, ], baseFilters: [], - palette: { type: 'palette', name: 'status' }, definitionFields: [ { field: 'monitor.name', nested: SYNTHETICS_STEP_NAME, singleSelection: true }, { field: 'url.full', filters: buildExistsFilter('summary.up', dataView) }, @@ -92,12 +91,14 @@ export function getSyntheticsKPIConfig({ dataView }: ConfigProps): SeriesConfig id: SUMMARY_UP, label: UP_LABEL, columnType: OPERATION_COLUMN, + palette: { type: 'palette', name: 'status' }, }, { field: SUMMARY_DOWN, id: SUMMARY_DOWN, label: DOWN_LABEL, columnType: OPERATION_COLUMN, + palette: { type: 'palette', name: 'status' }, }, { label: STEP_DURATION_LABEL, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx index 05b2b5859d275..b394b085a7c75 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/header/add_to_case_action.test.tsx @@ -11,6 +11,7 @@ import { fireEvent } from '@testing-library/dom'; import { AddToCaseAction } from './add_to_case_action'; import * as useCaseHook from '../hooks/use_add_to_case'; import * as datePicker from '../components/date_range_picker'; +import * as useGetUserCasesPermissionsModule from '../../../../hooks/use_get_user_cases_permissions'; import moment from 'moment'; describe('AddToCaseAction', function () { @@ -81,6 +82,10 @@ describe('AddToCaseAction', function () { }); it('should be able to click add to case button', async function () { + const mockUseGetUserCasesPermissions = jest + .spyOn(useGetUserCasesPermissionsModule, 'useGetUserCasesPermissions') + .mockImplementation(() => ({ crud: false, read: false })); + const initSeries = { data: [ { @@ -106,8 +111,13 @@ describe('AddToCaseAction', function () { expect(core?.cases?.ui.getAllCasesSelectorModal).toHaveBeenCalledWith( expect.objectContaining({ owner: ['observability'], - userCanCrud: true, + permissions: { + all: false, + read: false, + }, }) ); + + mockUseGetUserCasesPermissions.mockRestore(); }); }); 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 17cde17d8b7f9..118451b302948 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,6 +15,7 @@ import { GetAllCasesSelectorModalProps, } from '@kbn/cases-plugin/public'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; +import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions'; import { ObservabilityAppServices } from '../../../../application/types'; import { useAddToCase } from '../hooks/use_add_to_case'; import { observabilityFeatureId, observabilityAppId } from '../../../../../common'; @@ -38,6 +39,8 @@ export function AddToCaseAction({ timeRange, }: AddToCaseProps) { const kServices = useKibana<ObservabilityAppServices>().services; + const userPermissions = useGetUserCasesPermissions(); + const casesPermissions = { all: userPermissions.crud, read: userPermissions.read }; const { cases, @@ -74,8 +77,8 @@ export function AddToCaseAction({ }); const getAllCasesSelectorModalProps: GetAllCasesSelectorModalProps = { + permissions: casesPermissions, onRowClick: onCaseClicked, - userCanCrud: true, owner: [owner], onClose: () => { setIsCasesOpen(false); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 5632397584bb2..f369a65db704c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -68,6 +68,7 @@ export interface MetricOption { showPercentileAnnotations?: boolean; formula?: string; metricStateOptions?: Pick<MetricState, 'colorMode' | 'palette'>; + palette?: PaletteOutput; } export interface SeriesConfig { diff --git a/x-pack/plugins/observability/public/hooks/use_get_user_cases_permissions.tsx b/x-pack/plugins/observability/public/hooks/use_get_user_cases_permissions.tsx index 2827f89626794..83481c6d1f2f4 100644 --- a/x-pack/plugins/observability/public/hooks/use_get_user_cases_permissions.tsx +++ b/x-pack/plugins/observability/public/hooks/use_get_user_cases_permissions.tsx @@ -15,7 +15,10 @@ export interface UseGetUserCasesPermissions { } export function useGetUserCasesPermissions() { - const [casesPermissions, setCasesPermissions] = useState<UseGetUserCasesPermissions | null>(null); + const [casesPermissions, setCasesPermissions] = useState<UseGetUserCasesPermissions>({ + crud: false, + read: false, + }); const uiCapabilities = useKibana().services.application.capabilities; useEffect(() => { diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx index 0ead6468ad0b0..d903876c79f86 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_page/alerts_page.tsx @@ -220,6 +220,7 @@ function AlertsPage() { const CasesContext = cases.ui.getCasesContext(); const userPermissions = useGetUserCasesPermissions(); + const casesPermissions = { all: userPermissions.crud, read: userPermissions.read }; if (!hasAnyData && !isAllRequestsComplete) { return <LoadingObservability />; @@ -265,7 +266,7 @@ function AlertsPage() { <EuiFlexItem> <CasesContext owner={[observabilityFeatureId]} - userCanCrud={userPermissions?.crud ?? false} + permissions={casesPermissions} features={{ alerts: { sync: false } }} > <AlertsTableTGrid diff --git a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx index 0f2d23967dbcf..1b6f0b1860045 100644 --- a/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/containers/alerts_table_t_grid/alerts_table_t_grid.tsx @@ -201,7 +201,7 @@ function ObservabilityActions({ const actionsMenuItems = useMemo(() => { return [ - ...(casePermissions?.crud + ...(casePermissions.crud ? [ <EuiContextMenuItem data-test-subj="add-to-existing-case-action" @@ -246,7 +246,7 @@ function ObservabilityActions({ ], ]; }, [ - casePermissions?.crud, + casePermissions.crud, handleAddToExistingCaseClick, handleAddToNewCaseClick, linkToRule, diff --git a/x-pack/plugins/observability/public/pages/cases/cases.tsx b/x-pack/plugins/observability/public/pages/cases/cases.tsx index 0898f4aa8d071..e2d853efbf45f 100644 --- a/x-pack/plugins/observability/public/pages/cases/cases.tsx +++ b/x-pack/plugins/observability/public/pages/cases/cases.tsx @@ -13,17 +13,19 @@ import { usePluginContext } from '../../hooks/use_plugin_context'; import { LazyAlertsFlyout } from '../..'; import { useFetchAlertDetail } from './use_fetch_alert_detail'; import { useFetchAlertData } from './use_fetch_alert_data'; +import { UseGetUserCasesPermissions } from '../../hooks/use_get_user_cases_permissions'; interface CasesProps { - userCanCrud: boolean; + permissions: UseGetUserCasesPermissions; } -export const Cases = React.memo<CasesProps>(({ userCanCrud }) => { +export const Cases = React.memo<CasesProps>(({ permissions }) => { const { cases, application: { getUrlForApp, navigateToApp }, } = useKibana().services; const { observabilityRuleTypeRegistry } = usePluginContext(); const [selectedAlertId, setSelectedAlertId] = useState<string>(''); + const casesPermissions = { all: permissions.crud, read: permissions.read }; const handleFlyoutClose = useCallback(() => { setSelectedAlertId(''); @@ -44,7 +46,7 @@ export const Cases = React.memo<CasesProps>(({ userCanCrud }) => { )} {cases.ui.getCases({ basePath: CASES_PATH, - userCanCrud, + permissions: casesPermissions, owner: [CASES_OWNER], features: { alerts: { sync: false } }, useFetchAlertData, diff --git a/x-pack/plugins/observability/public/pages/cases/index.tsx b/x-pack/plugins/observability/public/pages/cases/index.tsx index 35651e29a6f7c..a3d1505d8f488 100644 --- a/x-pack/plugins/observability/public/pages/cases/index.tsx +++ b/x-pack/plugins/observability/public/pages/cases/index.tsx @@ -38,13 +38,13 @@ export const CasesPage = React.memo(() => { docsLink: docLinks.links.observability.guide, }); - return userPermissions == null || userPermissions?.read ? ( + return userPermissions.read ? ( <ObservabilityPageTemplate isPageDataLoaded={Boolean(hasAnyData || isAllRequestsComplete)} data-test-subj={noDataConfig ? 'noDataPage' : undefined} noDataConfig={noDataConfig} > - <Cases userCanCrud={userPermissions?.crud ?? false} /> + <Cases permissions={userPermissions} /> </ObservabilityPageTemplate> ) : ( <CaseFeatureNoPermissions /> diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 19f855b760cd4..dc0a6c9667400 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -128,6 +128,7 @@ export function OverviewPage({ routeParams }: Props) { const CasesContext = cases.ui.getCasesContext(); const userPermissions = useGetUserCasesPermissions(); + const casesPermissions = { all: userPermissions.crud, read: userPermissions.read }; useEffect(() => { if (hasAnyData !== true) { @@ -199,7 +200,7 @@ export function OverviewPage({ routeParams }: Props) { > <CasesContext owner={[observabilityFeatureId]} - userCanCrud={userPermissions?.crud ?? false} + permissions={casesPermissions} features={{ alerts: { sync: false } }} > <AlertsTableTGrid diff --git a/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx b/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx deleted file mode 100644 index 707c1204d1564..0000000000000 --- a/x-pack/plugins/observability/public/pages/rule_details/components/actions.test.tsx +++ /dev/null @@ -1,90 +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 from 'react'; -import { mount } from 'enzyme'; -import { nextTick } from '@kbn/test-jest-helpers'; -import { act } from 'react-dom/test-utils'; -import { Actions } from './actions'; -import { observabilityPublicPluginsStartMock } from '../../../observability_public_plugins_start.mock'; -import { kibanaStartMock } from '../../../utils/kibana_react.mock'; - -const mockUseKibanaReturnValue = kibanaStartMock.startContract(); - -jest.mock('../../../utils/kibana_react', () => ({ - __esModule: true, - useKibana: jest.fn(() => mockUseKibanaReturnValue), -})); - -jest.mock('@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api', () => ({ - loadAllActions: jest.fn(), -})); - -describe('Actions', () => { - async function setup() { - const ruleActions = [ - { - id: 1, - group: 'metrics.inventory_threshold.fired', - actionTypeId: '.server-log', - }, - { - id: 2, - group: 'metrics.inventory_threshold.fired', - actionTypeId: '.slack', - }, - ]; - const { loadAllActions } = jest.requireMock( - '@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api' - ); - loadAllActions.mockResolvedValueOnce([ - { - id: 'a0d2f6c0-e682-11ec-843b-213c67313f8c', - name: 'Email', - config: {}, - actionTypeId: '.email', - }, - { - id: 'f57cabc0-e660-11ec-8241-7deb55b17f15', - name: 'logs', - config: {}, - actionTypeId: '.server-log', - }, - { - id: '05b7ab30-e683-11ec-843b-213c67313f8c', - name: 'Slack', - actionTypeId: '.slack', - }, - ]); - - const actionTypeRegistryMock = - observabilityPublicPluginsStartMock.createStart().triggersActionsUi.actionTypeRegistry; - actionTypeRegistryMock.list.mockReturnValue([ - { id: '.server-log', iconClass: 'logsApp' }, - { id: '.slack', iconClass: 'logoSlack' }, - { id: '.email', iconClass: 'email' }, - { id: '.index', iconClass: 'indexOpen' }, - ]); - const wrapper = mount( - <Actions ruleActions={ruleActions} actionTypeRegistry={actionTypeRegistryMock} /> - ); - await act(async () => { - await nextTick(); - wrapper.update(); - }); - return wrapper; - } - - it("renders action connector icons for user's selected rule actions", async () => { - const wrapper = await setup(); - wrapper.debug(); - expect(wrapper.find('[data-euiicon-type]').length).toBe(2); - expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1); - expect(wrapper.find('[data-euiicon-type="logoSlack"]').length).toBe(1); - expect(wrapper.find('[data-euiicon-type="index"]').length).toBe(0); - expect(wrapper.find('[data-euiicon-type="email"]').length).toBe(0); - }); -}); diff --git a/x-pack/plugins/observability/public/pages/rule_details/components/index.ts b/x-pack/plugins/observability/public/pages/rule_details/components/index.ts index 8020af09dedc2..c4635fdca3e97 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/components/index.ts +++ b/x-pack/plugins/observability/public/pages/rule_details/components/index.ts @@ -8,4 +8,3 @@ export { PageTitle } from './page_title'; export { ItemTitleRuleSummary } from './item_title_rule_summary'; export { ItemValueRuleSummary } from './item_value_rule_summary'; -export { Actions } from './actions'; diff --git a/x-pack/plugins/observability/public/pages/rule_details/index.tsx b/x-pack/plugins/observability/public/pages/rule_details/index.tsx index 8cfc26493164a..6dc520cce971e 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/index.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/index.tsx @@ -42,6 +42,7 @@ import { import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; import { AlertConsumers } from '@kbn/rule-data-utils'; +import { RuleDefinitionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { DeleteModalConfirmation } from '../rules/components/delete_modal_confirmation'; import { CenterJustifiedSpinner } from '../rules/components/center_justified_spinner'; import { OBSERVABILITY_SOLUTIONS } from '../rules/config'; @@ -50,11 +51,10 @@ import { useBreadcrumbs } from '../../hooks/use_breadcrumbs'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useFetchRule } from '../../hooks/use_fetch_rule'; import { RULES_BREADCRUMB_TEXT } from '../rules/translations'; -import { PageTitle, ItemTitleRuleSummary, ItemValueRuleSummary, Actions } from './components'; +import { PageTitle, ItemTitleRuleSummary, ItemValueRuleSummary } from './components'; import { useKibana } from '../../utils/kibana_react'; import { useFetchLast24hAlerts } from '../../hooks/use_fetch_last24h_alerts'; import { useFetchLast24hRuleExecutionLog } from '../../hooks/use_fetch_last24h_rule_execution_log'; -import { formatInterval } from './utils'; import { hasExecuteActionsCapability, hasAllPrivilege } from './config'; import { paths } from '../../config/paths'; import { observabilityFeatureId } from '../../../common'; @@ -67,9 +67,9 @@ export function RuleDetailsPage() { ruleTypeRegistry, getRuleStatusDropdown, getEditAlertFlyout, - actionTypeRegistry, getRuleEventLogList, getAlertsStateTable, + getRuleDefinition, }, application: { capabilities, navigateToUrl }, notifications: { toasts }, @@ -79,7 +79,7 @@ export function RuleDetailsPage() { const { ObservabilityPageTemplate } = usePluginContext(); const { isRuleLoading, rule, errorRule, reloadRule } = useFetchRule({ ruleId, http }); const { isLoadingExecutionLog, executionLog } = useFetchLast24hRuleExecutionLog({ http, ruleId }); - const { ruleTypes, ruleTypeIndex } = useLoadRuleTypes({ + const { ruleTypes } = useLoadRuleTypes({ filteredSolutions: OBSERVABILITY_SOLUTIONS, }); @@ -160,19 +160,6 @@ export function RuleDetailsPage() { ? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext : false); - const getRuleConditionsWording = () => { - const numberOfConditions = rule?.params.criteria ? (rule?.params.criteria as any[]).length : 0; - return ( - <> - {numberOfConditions}  - {i18n.translate('xpack.observability.ruleDetails.conditions', { - defaultMessage: 'condition{s}', - values: { s: numberOfConditions > 1 ? 's' : '' }, - })} - </> - ); - }; - const alertStateProps = { alertsTableConfigurationRegistry, configurationId: observabilityFeatureId, @@ -257,9 +244,6 @@ export function RuleDetailsPage() { unsnoozeRule: async (scheduleIds) => await unsnoozeRule({ http, id: rule.id, scheduleIds }), }); - const getNotifyText = () => - NOTIFY_WHEN_OPTIONS.current.find((option) => option.value === rule?.notifyWhen)?.inputDisplay || - rule.notifyWhen; return ( <ObservabilityPageTemplate data-test-subj="ruleDetails" @@ -412,113 +396,7 @@ export function RuleDetailsPage() { </EuiFlexGroup> </EuiPanel> </EuiFlexItem> - - {/* Right side of Rule Summary */} - - <EuiFlexItem data-test-subj="ruleSummaryRuleDefinition" grow={3}> - <EuiPanel color="subdued" hasBorder={false} paddingSize={'m'}> - <EuiFlexGroup justifyContent="spaceBetween"> - <EuiTitle size="s"> - <EuiFlexItem grow={false}> - {i18n.translate('xpack.observability.ruleDetails.definition', { - defaultMessage: 'Definition', - })} - </EuiFlexItem> - </EuiTitle> - {hasEditButton && ( - <EuiFlexItem grow={false}> - <EuiButtonEmpty iconType={'pencil'} onClick={() => setEditFlyoutVisible(true)} /> - </EuiFlexItem> - )} - </EuiFlexGroup> - - <EuiSpacer size="m" /> - - <EuiFlexGroup alignItems="baseline"> - <EuiFlexItem> - <EuiFlexGroup> - <ItemTitleRuleSummary> - {i18n.translate('xpack.observability.ruleDetails.ruleType', { - defaultMessage: 'Rule type', - })} - </ItemTitleRuleSummary> - <ItemValueRuleSummary - data-test-subj="ruleSummaryRuleType" - itemValue={ruleTypeIndex.get(rule.ruleTypeId)?.name || rule.ruleTypeId} - /> - </EuiFlexGroup> - - <EuiSpacer size="m" /> - - <EuiFlexGroup alignItems="flexStart" responsive={false}> - <ItemTitleRuleSummary> - {i18n.translate('xpack.observability.ruleDetails.description', { - defaultMessage: 'Description', - })} - </ItemTitleRuleSummary> - <ItemValueRuleSummary - itemValue={ruleTypeRegistry.get(rule.ruleTypeId).description} - /> - </EuiFlexGroup> - - <EuiSpacer size="m" /> - - <EuiFlexGroup> - <ItemTitleRuleSummary> - {i18n.translate('xpack.observability.ruleDetails.conditionsTitle', { - defaultMessage: 'Conditions', - })} - </ItemTitleRuleSummary> - <EuiFlexItem grow={3}> - <EuiFlexGroup alignItems="center"> - {hasEditButton ? ( - <EuiButtonEmpty onClick={() => setEditFlyoutVisible(true)}> - <EuiText size="s">{getRuleConditionsWording()}</EuiText> - </EuiButtonEmpty> - ) : ( - <EuiText size="s">{getRuleConditionsWording()}</EuiText> - )} - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - <EuiFlexItem> - <EuiFlexGroup> - <ItemTitleRuleSummary> - {i18n.translate('xpack.observability.ruleDetails.runsEvery', { - defaultMessage: 'Runs every', - })} - </ItemTitleRuleSummary> - - <ItemValueRuleSummary itemValue={formatInterval(rule.schedule.interval)} /> - </EuiFlexGroup> - - <EuiSpacer size="m" /> - - <EuiFlexGroup> - <ItemTitleRuleSummary> - {i18n.translate('xpack.observability.ruleDetails.notifyWhen', { - defaultMessage: 'Notify', - })} - </ItemTitleRuleSummary> - <ItemValueRuleSummary itemValue={String(getNotifyText())} /> - </EuiFlexGroup> - - <EuiSpacer size="m" /> - <EuiFlexGroup alignItems="baseline"> - <ItemTitleRuleSummary> - {i18n.translate('xpack.observability.ruleDetails.actions', { - defaultMessage: 'Actions', - })} - </ItemTitleRuleSummary> - <EuiFlexItem grow={3}> - <Actions ruleActions={rule.actions} actionTypeRegistry={actionTypeRegistry} /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlexItem> - </EuiFlexGroup> - </EuiPanel> - </EuiFlexItem> + {getRuleDefinition({ rule, onEditRule: () => reloadRule() } as RuleDefinitionProps)} </EuiFlexGroup> <EuiSpacer size="l" /> diff --git a/x-pack/plugins/observability/public/pages/rule_details/translations.ts b/x-pack/plugins/observability/public/pages/rule_details/translations.ts index bda8284c31a9e..579a709c0857c 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/translations.ts +++ b/x-pack/plugins/observability/public/pages/rule_details/translations.ts @@ -12,12 +12,6 @@ export const RULE_LOAD_ERROR = (errorMessage: string) => values: { message: errorMessage }, }); -export const ACTIONS_LOAD_ERROR = (errorMessage: string) => - i18n.translate('xpack.observability.ruleDetails.connectorsLoadError', { - defaultMessage: 'Unable to load rule actions connectors. Reason: {message}', - values: { message: errorMessage }, - }); - export const EXECUTION_LOG_ERROR = (errorMessage: string) => i18n.translate('xpack.observability.ruleDetails.executionLogError', { defaultMessage: 'Unable to load rule execution log. Reason: {message}', diff --git a/x-pack/plugins/observability/public/pages/rule_details/utils.ts b/x-pack/plugins/observability/public/pages/rule_details/utils.ts deleted file mode 100644 index 0c907d93228a6..0000000000000 --- a/x-pack/plugins/observability/public/pages/rule_details/utils.ts +++ /dev/null @@ -1,15 +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 { formatDurationFromTimeUnitChar, TimeUnitChar } from '../../../common'; - -export const formatInterval = (ruleInterval: string) => { - const interval: string[] | null = ruleInterval.match(/(^\d*)([s|m|h|d])/); - if (!interval || interval.length < 3) return ruleInterval; - const value: number = +interval[1]; - const unit = interval[2] as TimeUnitChar; - return formatDurationFromTimeUnitChar(value, unit); -}; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index ecdbd99e4770a..2fb5a42dab286 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -6,7 +6,8 @@ */ import { errors as esErrors } from '@elastic/elasticsearch'; -import type { IScopedClusterClient, IUiSettingsClient, SearchResponse } from '@kbn/core/server'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { IScopedClusterClient, IUiSettingsClient } from '@kbn/core/server'; import { identity, range } from 'lodash'; import * as Rx from 'rxjs'; import { diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index 20f293f37fc26..7b44fb0893362 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -209,6 +209,7 @@ describe('SecurityNavControl', () => { } } />, + "onClick": [Function], }, Object { "data-test-subj": "userMenuLink__link1", @@ -326,6 +327,7 @@ describe('SecurityNavControl', () => { } } />, + "onClick": [Function], }, Object { "data-test-subj": "logoutLink", diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 1a32a083793f3..2f462aaba4bc6 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -45,7 +45,7 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({ userMenuLinks$, }) => { const userMenuLinks = useObservable(userMenuLinks$, []); - const [isOpen, setIsOpen] = useState(false); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); const userProfile = useUserProfile<{ avatar: UserAvatarData }>('avatar'); const currentUser = useCurrentUser(); // User profiles do not exist for anonymous users so need to fetch current user as well @@ -55,12 +55,12 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({ const button = ( <EuiHeaderSectionItemButton aria-controls="headerUserMenu" - aria-expanded={isOpen} + aria-expanded={isPopoverOpen} aria-haspopup="true" aria-label={i18n.translate('xpack.security.navControlComponent.accountMenuAriaLabel', { defaultMessage: 'Account menu', })} - onClick={() => setIsOpen((value) => (currentUser.value ? !value : false))} + onClick={() => setIsPopoverOpen((value) => (currentUser.value ? !value : false))} data-test-subj="userMenuButton" style={{ lineHeight: 'normal' }} > @@ -105,6 +105,9 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({ ), icon: <EuiIcon type={hasCustomProfileLinks ? 'controlsHorizontal' : 'user'} size="m" />, href: editProfileUrl, + onClick: () => { + setIsPopoverOpen(false); + }, 'data-test-subj': 'profileLink', }; @@ -138,10 +141,10 @@ export const SecurityNavControl: FunctionComponent<SecurityNavControlProps> = ({ id="headerUserMenu" ownFocus button={button} - isOpen={isOpen} + isOpen={isPopoverOpen} anchorPosition="downRight" repositionOnScroll - closePopover={() => setIsOpen(false)} + closePopover={() => setIsPopoverOpen(false)} panelPaddingSize="none" buffer={0} > diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index b1c98ce2ab0ac..1dc8c687de436 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -75,41 +75,43 @@ describe('SecurityNavControlService', () => { expect(target).toMatchInlineSnapshot(` <div> - <div - class="euiPopover euiPopover--anchorDownRight" - id="headerUserMenu" - > + <div> <div - class="euiPopover__anchor" + class="euiPopover euiPopover--anchorDownRight" + id="headerUserMenu" > - <button - aria-controls="headerUserMenu" - aria-expanded="false" - aria-haspopup="true" - aria-label="Account menu" - class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" - data-test-subj="userMenuButton" - style="line-height: normal;" - type="button" + <div + class="euiPopover__anchor" > - <span - class="euiButtonContent euiButtonEmpty__content" + <button + aria-controls="headerUserMenu" + aria-expanded="false" + aria-haspopup="true" + aria-label="Account menu" + class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" + data-test-subj="userMenuButton" + style="line-height: normal;" + type="button" > <span - class="euiButtonEmpty__text" + class="euiButtonContent euiButtonEmpty__content" > <span - class="euiHeaderSectionItemButton__content" + class="euiButtonEmpty__text" > <span - aria-label="Loading" - class="euiLoadingSpinner emotion-euiLoadingSpinner-m" - role="progressbar" - /> + class="euiHeaderSectionItemButton__content" + > + <span + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + role="progressbar" + /> + </span> </span> </span> - </span> - </button> + </button> + </div> </div> </div> </div> diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index 592f5d16f523e..91d0c33ade107 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -16,6 +16,7 @@ import { map, takeUntil } from 'rxjs/operators'; import type { CoreStart, CoreTheme } from '@kbn/core/public'; import { I18nProvider } from '@kbn/i18n-react'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import type { SecurityLicense } from '../../common/licensing'; import type { AuthenticationServiceSetup } from '../authentication'; @@ -166,7 +167,9 @@ export const Providers: FunctionComponent<ProvidersProps> = ({ <AuthenticationProvider authc={authc}> <SecurityApiClientsProvider {...securityApiClients}> <I18nProvider> - <KibanaThemeProvider theme$={theme$}>{children}</KibanaThemeProvider> + <KibanaThemeProvider theme$={theme$}> + <RedirectAppLinks coreStart={services}>{children}</RedirectAppLinks> + </KibanaThemeProvider> </I18nProvider> </SecurityApiClientsProvider> </AuthenticationProvider> diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 7f47e0d18059e..86793045ad0cd 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -123,6 +123,10 @@ export enum SecurityPageName { exploreLanding = 'explore', dashboardsLanding = 'dashboards', noPage = '', + cloudSecurityPosture = 'cloud_security_posture', + cloudSecurityPostureDashboard = 'cloud_security_posture-dashboard', + cloudSecurityPostureFindings = 'cloud_security_posture-findings', + cloudSecurityPostureBenchmarks = 'cloud_security_posture-benchmarks', } export const EXPLORE_PATH = '/explore' as const; @@ -151,6 +155,10 @@ export const HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/host_isolation_exceptions` as const; export const BLOCKLIST_PATH = `${MANAGEMENT_PATH}/blocklist` as const; export const RESPONSE_ACTIONS_PATH = `${MANAGEMENT_PATH}/response_actions` as const; +export const CLOUD_SECURITY_POSTURE_PATH = '/cloud_security_posture' as const; +export const CLOUD_SECURITY_POSTURE_DASHBOARD_PATH = '/cloud_security_posture/dashboard' as const; +export const CLOUD_SECURITY_POSTURE_FINDINGS_PATH = '/cloud_security_posture/findings' as const; +export const CLOUD_SECURITY_POSTURE_BENCHMARKS_PATH = '/cloud_security_posture/benchmarks' as const; export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const; export const APP_LANDING_PATH = `${APP_PATH}${LANDING_PATH}` as const; @@ -382,6 +390,11 @@ export const WARNING_TRANSFORM_STATES = new Set([ TRANSFORM_STATES.STOPPING, ]); +export const STARTED_TRANSFORM_STATES = new Set([ + TRANSFORM_STATES.INDEXING, + TRANSFORM_STATES.STARTED, +]); + /** * How many rules to update at a time is set to 50 from errors coming from * the slow environments such as cloud when the rule updates are > 100 we were diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts index 248b3e4c8beb7..3ae72f8b903d7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - AddPrepackagedRulesSchema, - AddPrepackagedRulesSchemaDecoded, -} from './add_prepackaged_rules_schema'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; +import { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ description: 'some description', @@ -23,33 +19,6 @@ export const getAddPrepackagedRulesSchemaMock = (): AddPrepackagedRulesSchema => version: 1, }); -export const getAddPrepackagedRulesSchemaDecodedMock = (): AddPrepackagedRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'query', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: false, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', -}); - export const getAddPrepackagedThreatMatchRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ description: 'some description', name: 'Query with a rule id', @@ -93,61 +62,18 @@ export const getAddPrepackagedThreatMatchRulesSchemaMock = (): AddPrepackagedRul ], }); -export const getAddPrepackagedThreatMatchRulesSchemaDecodedMock = - (): AddPrepackagedRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'threat_match', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: false, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - threat_query: '*:*', - threat_index: ['list-index'], - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [ - { - bool: { - must: [ - { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', - }, - }, - ], - filter: [], - should: [], - must_not: [], - }, - }, - ], - }); +export const getAddPrepackagedThresholdRulesSchemaMock = (): AddPrepackagedRulesSchema => ({ + description: 'some description', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + version: 1, + type: 'threshold', + threshold: { + field: ['field-1', 'field-2', 'field-3', 'field-4'], + value: 1, + }, +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts index 9d0456c507772..f8c901b3311df 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.test.ts @@ -7,7 +7,6 @@ import { addPrepackagedRulesSchema, - AddPrepackagedRulesSchemaDecoded, AddPrepackagedRulesSchema, } from './add_prepackaged_rules_schema'; @@ -16,11 +15,8 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { getAddPrepackagedRulesSchemaMock, - getAddPrepackagedRulesSchemaDecodedMock, getAddPrepackagedThreatMatchRulesSchemaMock, - getAddPrepackagedThreatMatchRulesSchemaDecodedMock, } from './add_prepackaged_rules_schema.mock'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; import { getListArrayMock } from '../types/lists.mock'; describe('add prepackaged rules schema', () => { @@ -30,15 +26,24 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "description"', - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "rule_id"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "rule_id"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -63,14 +68,21 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "description"', - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -83,13 +95,18 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -103,13 +120,18 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -124,13 +146,18 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -146,12 +173,15 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -168,11 +198,12 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "version"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "version"' + ); expect(message.schema).toEqual({}); }); @@ -262,33 +293,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -337,34 +341,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does not validate', () => { @@ -393,31 +369,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index, version] does not validate because output_index is not allowed', () => { - const payload: Partial<AddPrepackagedRulesSchema> & { output_index: string } = { - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - version: 1, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "output_index"']); - expect(message.schema).toEqual({}); - }); - test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => { const payload: Partial<AddPrepackagedRulesSchema> = { rule_id: 'rule-1', @@ -437,32 +388,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - version: 1, - actions: [], - enabled: false, - exceptions_list: [], - false_positives: [], - max_signals: 100, - references: [], - tags: [], - threat: [], - throttle: null, - }; - expect(message.schema).toEqual(expected); }); test('You can send in a namespace', () => { @@ -475,11 +400,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - namespace: 'a namespace', - }; - expect(message.schema).toEqual(expected); }); test('You can send in an empty array to threat', () => { @@ -492,11 +412,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - threat: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { @@ -535,48 +450,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - threat: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('allows references to be sent as valid', () => { @@ -589,24 +462,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - references: ['index-1'], - }; - expect(message.schema).toEqual(expected); - }); - - test('defaults references to an array if it is not sent in', () => { - const { references, ...noReferences } = getAddPrepackagedRulesSchemaMock(); - const decoded = addPrepackagedRulesSchema.decode(noReferences); - const checked = exactCheck(noReferences, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - references: [], - }; - expect(message.schema).toEqual(expected); }); test('immutable cannot be set in a pre-packaged rule', () => { @@ -622,17 +477,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('defaults enabled to false', () => { - const payload: AddPrepackagedRulesSchema = getAddPrepackagedRulesSchemaMock(); - delete payload.enabled; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as unknown as AddPrepackagedRulesSchemaDecoded).enabled).toEqual(false); - }); - test('rule_id is required', () => { const payload: AddPrepackagedRulesSchema = getAddPrepackagedRulesSchemaMock(); // @ts-expect-error @@ -673,49 +517,8 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('defaults interval to 5 min', () => { - const { interval, ...noInterval } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noInterval, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { interval: expectedInterval, ...expectedNoInterval } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoInterval, - interval: '5m', - }; - expect(message.schema).toEqual(expected); - }); - - test('defaults max signals to 100', () => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { max_signals, ...noMaxSignals } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noMaxSignals, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { max_signals: expectedMaxSignals, ...expectedNoMaxSignals } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoMaxSignals, - max_signals: 100, - }; - expect(message.schema).toEqual(expected); - }); - test('saved_query type can have filters with it', () => { - const payload: AddPrepackagedRulesSchema = { + const payload = { ...getAddPrepackagedRulesSchemaMock(), filters: [], }; @@ -724,11 +527,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - filters: [], - }; - expect(message.schema).toEqual(expected); }); test('filters cannot be a string', () => { @@ -747,7 +545,7 @@ describe('add prepackaged rules schema', () => { }); test('language validates with kuery', () => { - const payload: AddPrepackagedRulesSchema = { + const payload = { ...getAddPrepackagedRulesSchemaMock(), language: 'kuery', }; @@ -756,15 +554,10 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - language: 'kuery', - }; - expect(message.schema).toEqual(expected); }); test('language validates with lucene', () => { - const payload: AddPrepackagedRulesSchema = { + const payload = { ...getAddPrepackagedRulesSchemaMock(), language: 'lucene', }; @@ -773,11 +566,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - language: 'lucene', - }; - expect(message.schema).toEqual(expected); }); test('language does not validate with something made up', () => { @@ -833,11 +621,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - max_signals: 1, - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of tags', () => { @@ -850,11 +633,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - tags: ['tag_1', 'tag_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of tags that are numbers', () => { @@ -955,20 +733,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - threat: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of false positives', () => { @@ -981,11 +745,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - false_positives: ['false_1', 'false_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of false positives that are numbers', () => { @@ -1043,11 +802,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - risk_score: 0, - }; - expect(message.schema).toEqual(expected); }); test('You can set the risk_score to 100', () => { @@ -1060,11 +814,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - risk_score: 100, - }; - expect(message.schema).toEqual(expected); }); test('You can set meta to any object you want', () => { @@ -1079,13 +828,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }; - expect(message.schema).toEqual(expected); }); test('You cannot create meta as a string', () => { @@ -1103,26 +845,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('You can omit the query string when filters are present', () => { - const { query, ...noQuery } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noQuery, - filters: [], - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { query: expectedQuery, ...expectedNoQuery } = getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoQuery, - filters: [], - }; - expect(message.schema).toEqual(expected); - }); - test('validates with timeline_id and timeline_title', () => { const payload: AddPrepackagedRulesSchema = { ...getAddPrepackagedRulesSchemaMock(), @@ -1134,50 +856,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - timeline_id: 'timeline-id', - timeline_title: 'timeline-title', - }; - expect(message.schema).toEqual(expected); - }); - - test('The default for "from" will be "now-6m"', () => { - const { from, ...noFrom } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noFrom, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { from: expectedFrom, ...expectedNoFrom } = getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoFrom, - from: 'now-6m', - }; - expect(message.schema).toEqual(expected); - }); - - test('The default for "to" will be "now"', () => { - const { to, ...noTo } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noTo, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { to: expectedTo, ...expectedNoTo } = getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoTo, - to: 'now', - }; - expect(message.schema).toEqual(expected); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { @@ -1193,26 +871,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('The default for "actions" will be an empty array', () => { - const { actions, ...noActions } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noActions, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { actions: expectedActions, ...expectedNoActions } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoActions, - actions: [], - }; - expect(message.schema).toEqual(expected); - }); - test('You cannot send in an array of actions that are missing "group"', () => { const payload: Omit<AddPrepackagedRulesSchema['actions'], 'group'> = { ...getAddPrepackagedRulesSchemaMock(), @@ -1295,26 +953,6 @@ describe('add prepackaged rules schema', () => { expect(message.schema).toEqual({}); }); - test('The default for "throttle" will be null', () => { - const { throttle, ...noThrottle } = getAddPrepackagedRulesSchemaMock(); - const payload: AddPrepackagedRulesSchema = { - ...noThrottle, - }; - - const decoded = addPrepackagedRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { throttle: expectedThrottle, ...expectedNoThrottle } = - getAddPrepackagedRulesSchemaDecodedMock(); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...expectedNoThrottle, - throttle: null, - }; - expect(message.schema).toEqual(expected); - }); - describe('note', () => { test('You can set note to a string', () => { const payload: AddPrepackagedRulesSchema = { @@ -1326,11 +964,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - note: '# documentation markdown here', - }; - expect(message.schema).toEqual(expected); }); test('You can set note to an empty string', () => { @@ -1343,11 +976,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaDecodedMock(), - note: '', - }; - expect(message.schema).toEqual(expected); }); test('You cannot create note as an object', () => { @@ -1387,33 +1015,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1440,47 +1041,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - filters: [], - exceptions_list: [ - { - id: 'some_uuid', - list_id: 'list_id_single', - namespace_type: 'single', - type: 'detection', - }, - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, version, and empty exceptions_list] does validate', () => { @@ -1505,40 +1065,10 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - filters: [], - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, version, and invalid exceptions_list] does NOT validate', () => { - const payload: Omit<AddPrepackagedRulesSchema, 'exceptions_list'> & { - exceptions_list: Array<{ id: string; namespace_type: string }>; - } = { + const payload = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1587,34 +1117,6 @@ describe('add prepackaged rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: AddPrepackagedRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: false, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - filters: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1624,9 +1126,7 @@ describe('add prepackaged rules schema', () => { const decoded = addPrepackagedRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getAddPrepackagedThreatMatchRulesSchemaDecodedMock(); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); }); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts index 72c48a139ca1d..a836fc2ba2c10 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts @@ -7,188 +7,28 @@ import * as t from 'io-ts'; -import { - Actions, - DefaultActionsArray, - DefaultFromString, - DefaultIntervalString, - DefaultMaxSignalsNumber, - DefaultRiskScoreMappingArray, - DefaultSeverityMappingArray, - DefaultThreatArray, - DefaultThrottleNull, - DefaultToString, - From, - RiskScoreMapping, - machine_learning_job_id, - risk_score, - threat_index, - concurrent_searches, - items_per_search, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - Threats, - type, - language, - severity, - SeverityMapping, - ThrottleOrNull, - MaxSignals, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { - version, - DefaultStringArray, - DefaultBooleanFalse, -} from '@kbn/securitysolution-io-ts-types'; +import { version } from '@kbn/securitysolution-io-ts-types'; -import { DefaultListArray, ListArray } from '@kbn/securitysolution-io-ts-list-types'; -import { - description, - anomaly_threshold, - filters, - index, - data_view_id, - saved_id, - timeline_id, - timeline_title, - meta, - name, - Tags, - To, - threshold, - note, - References, - Enabled, - FalsePositives, - Interval, - query, - rule_id, - building_block_type, - license, - rule_name_override, - timestamp_override, - Author, - timestamp_field, - event_category_override, - tiebreaker_field, - namespace, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../common'; +import { rule_id, RelatedIntegrationArray, RequiredFieldArray, SetupGuide } from '../common'; +import { baseCreateParams, createTypeSpecific } from './rule_schemas'; /** * Big differences between this schema and the createRulesSchema * - rule_id is required here - * - output_index is not allowed (and instead the space index must be used) - * - immutable is forbidden but defaults to true instead of to false and it can only ever be true (This is forced directly in the route and not here) - * - enabled defaults to false instead of true * - version is a required field that must exist - * - index is a required field that must exist if type !== machine_learning (Checked within the runtime type dependent system) */ export const addPrepackagedRulesSchema = t.intersection([ - t.exact( - t.type({ - description, - risk_score, - name, - severity, - type, - rule_id, - version, - }) - ), + baseCreateParams, + createTypeSpecific, + // version is required in addPrepackagedRulesSchema, so this supercedes the defaultable + // version in baseParams + t.exact(t.type({ rule_id, version })), t.exact( t.partial({ - actions: DefaultActionsArray, // defaults to empty actions array if not set during decode - anomaly_threshold, // defaults to undefined if not set during decode - author: DefaultStringArray, // defaults to empty array of strings if not set during decode - building_block_type, // defaults to undefined if not set during decode - enabled: DefaultBooleanFalse, // defaults to false if not set during decode - timestamp_field, // defaults to "undefined" if not set during decode - event_category_override, // defaults to "undefined" if not set during decode - tiebreaker_field, // defaults to "undefined" if not set during decode - false_positives: DefaultStringArray, // defaults to empty string array if not set during decode - filters, // defaults to undefined if not set during decode - from: DefaultFromString, // defaults to "now-6m" if not set during decode - index, // defaults to undefined if not set during decode - data_view_id, // defaults to undefined if not set during decode - interval: DefaultIntervalString, // defaults to "5m" if not set during decode - query, // defaults to undefined if not set during decode - language, // defaults to undefined if not set during decode - license, // defaults to "undefined" if not set during decode - saved_id, // defaults to "undefined" if not set during decode - timeline_id, // defaults to "undefined" if not set during decode - timeline_title, // defaults to "undefined" if not set during decode - meta, // defaults to "undefined" if not set during decode - machine_learning_job_id, // defaults to "undefined" if not set during decode - max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode - related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode - required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode - risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode - rule_name_override, // defaults to "undefined" if not set during decode - setup: SetupGuide, // defaults to "undefined" if not set during decode - severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode - tags: DefaultStringArray, // defaults to empty string array if not set during decode - to: DefaultToString, // defaults to "now" if not set during decode - threat: DefaultThreatArray, // defaults to empty array if not set during decode - threshold, // defaults to "undefined" if not set during decode - throttle: DefaultThrottleNull, // defaults to "null" if not set during decode - timestamp_override, // defaults to "undefined" if not set during decode - references: DefaultStringArray, // defaults to empty array of strings if not set during decode - note, // defaults to "undefined" if not set during decode - exceptions_list: DefaultListArray, // defaults to empty array if not set during decode - threat_filters, // defaults to "undefined" if not set during decode - threat_mapping, // defaults to "undefined" if not set during decode - threat_query, // defaults to "undefined" if not set during decode - threat_index, // defaults to "undefined" if not set during decode - threat_language, // defaults "undefined" if not set during decode - threat_indicator_path, // defaults "undefined" if not set during decode - concurrent_searches, // defaults to "undefined" if not set during decode - items_per_search, // defaults to "undefined" if not set during decode - namespace, // defaults to "undefined" if not set during decode + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, }) ), ]); export type AddPrepackagedRulesSchema = t.TypeOf<typeof addPrepackagedRulesSchema>; - -// This type is used after a decode since some things are defaults after a decode. -export type AddPrepackagedRulesSchemaDecoded = Omit< - AddPrepackagedRulesSchema, - | 'author' - | 'references' - | 'actions' - | 'enabled' - | 'false_positives' - | 'from' - | 'interval' - | 'max_signals' - | 'namespace' - | 'risk_score_mapping' - | 'severity_mapping' - | 'tags' - | 'to' - | 'threat' - | 'throttle' - | 'exceptions_list' -> & { - author: Author; - references: References; - actions: Actions; - enabled: Enabled; - false_positives: FalsePositives; - from: From; - interval: Interval; - max_signals: MaxSignals; - risk_score_mapping: RiskScoreMapping; - severity_mapping: SeverityMapping; - tags: Tags; - to: To; - threat: Threats; - throttle: ThrottleOrNull; - exceptions_list: ListArray; - namespace?: string; -}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts index 738825bb166cc..5eb7829c16d97 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.test.ts @@ -10,26 +10,6 @@ import { addPrepackagedRuleValidateTypeDependents } from './add_prepackaged_rule import { getAddPrepackagedRulesSchemaMock } from './add_prepackaged_rules_schema.mock'; describe('add_prepackaged_rules_type_dependents', () => { - test('saved_id is required when type is saved_query and will not validate without out', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'saved_query', - }; - delete schema.saved_id; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "saved_query", "saved_id" is required']); - }); - - test('saved_id is required when type is saved_query and validates with it', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'saved_query', - saved_id: '123', - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - test('You cannot omit timeline_title when timeline_id is present', () => { const schema: AddPrepackagedRulesSchema = { ...getAddPrepackagedRulesSchemaMock(), @@ -70,17 +50,8 @@ describe('add_prepackaged_rules_type_dependents', () => { expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); - test('threshold is required when type is threshold and validates with it', () => { - const schema: AddPrepackagedRulesSchema = { - ...getAddPrepackagedRulesSchemaMock(), - type: 'threshold', - }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']); - }); - test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { - const schema: AddPrepackagedRulesSchema = { + const schema = { ...getAddPrepackagedRulesSchemaMock(), type: 'threshold', threshold: { @@ -88,12 +59,12 @@ describe('add_prepackaged_rules_type_dependents', () => { value: -1, }, }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); + const errors = addPrepackagedRuleValidateTypeDependents(schema as AddPrepackagedRulesSchema); expect(errors).toEqual(['"threshold.value" has to be bigger than 0']); }); test('threshold.field should contain 3 items or less', () => { - const schema: AddPrepackagedRulesSchema = { + const schema = { ...getAddPrepackagedRulesSchemaMock(), type: 'threshold', threshold: { @@ -101,12 +72,12 @@ describe('add_prepackaged_rules_type_dependents', () => { value: 1, }, }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); + const errors = addPrepackagedRuleValidateTypeDependents(schema as AddPrepackagedRulesSchema); expect(errors).toEqual(['Number of fields must be 3 or less']); }); test('threshold.cardinality[0].field should not be in threshold.field', () => { - const schema: AddPrepackagedRulesSchema = { + const schema = { ...getAddPrepackagedRulesSchemaMock(), type: 'threshold', threshold: { @@ -120,7 +91,7 @@ describe('add_prepackaged_rules_type_dependents', () => { ], }, }; - const errors = addPrepackagedRuleValidateTypeDependents(schema); + const errors = addPrepackagedRuleValidateTypeDependents(schema as AddPrepackagedRulesSchema); expect(errors).toEqual(['Cardinality of a field that is being aggregated on is always 1']); }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts index fb47e4a90c1bd..a850259936648 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_type_dependents.ts @@ -5,70 +5,8 @@ * 2.0. */ -import { isMlRule } from '../../../machine_learning/helpers'; -import { isThresholdRule } from '../../utils'; import { AddPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; -export const validateAnomalyThreshold = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.anomaly_threshold == null) { - return ['when "type" is "machine_learning" anomaly_threshold is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateQuery = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.query != null) { - return ['when "type" is "machine_learning", "query" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateLanguage = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.language != null) { - return ['when "type" is "machine_learning", "language" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateSavedId = (rule: AddPrepackagedRulesSchema): string[] => { - if (rule.type === 'saved_query') { - if (rule.saved_id == null) { - return ['when "type" is "saved_query", "saved_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateMachineLearningJobId = (rule: AddPrepackagedRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.machine_learning_job_id == null) { - return ['when "type" is "machine_learning", "machine_learning_job_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - export const validateTimelineId = (rule: AddPrepackagedRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { @@ -97,7 +35,7 @@ export const validateTimelineTitle = (rule: AddPrepackagedRulesSchema): string[] export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => { const errors: string[] = []; - if (isThresholdRule(rule.type)) { + if (rule.type === 'threshold') { if (!rule.threshold) { errors.push('when "type" is "threshold", "threshold" is required'); } else { @@ -121,14 +59,5 @@ export const validateThreshold = (rule: AddPrepackagedRulesSchema): string[] => export const addPrepackagedRuleValidateTypeDependents = ( rule: AddPrepackagedRulesSchema ): string[] => { - return [ - ...validateAnomalyThreshold(rule), - ...validateQuery(rule), - ...validateLanguage(rule), - ...validateSavedId(rule), - ...validateMachineLearningJobId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; + return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts index 6c7c03b0b4fa5..a827852a4166d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.mock.ts @@ -5,8 +5,7 @@ * 2.0. */ -import { ImportRulesSchema, ImportRulesSchemaDecoded } from './import_rules_schema'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; +import { ImportRulesSchema } from './import_rules_schema'; export const getImportRulesSchemaMock = (ruleId = 'rule-1'): ImportRulesSchema => ({ description: 'some description', @@ -31,34 +30,6 @@ export const getImportRulesWithIdSchemaMock = (ruleId = 'rule-1'): ImportRulesSc rule_id: ruleId, }); -export const getImportRulesSchemaDecodedMock = (): ImportRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'query', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - immutable: false, -}); - /** * Given an array of rules, builds an NDJSON string of rules * as we might import/export @@ -109,62 +80,3 @@ export const getImportThreatMatchRulesSchemaMock = (ruleId = 'rule-1'): ImportRu }, ], }); - -export const getImportThreatMatchRulesSchemaDecodedMock = (): ImportRulesSchemaDecoded => ({ - author: [], - description: 'some description', - name: 'Query with a rule id', - query: 'user.name: root or user.name: admin', - severity: 'high', - severity_mapping: [], - type: 'threat_match', - risk_score: 55, - risk_score_mapping: [], - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - from: 'now-6m', - interval: '5m', - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - to: 'now', - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - rule_id: 'rule-1', - immutable: false, - threat_query: '*:*', - threat_index: ['index-123'], - threat_mapping: [ - { - entries: [ - { - field: 'host.name', - value: 'host.name', - type: 'mapping', - }, - ], - }, - ], - threat_filters: [ - { - bool: { - must: [ - { - query_string: { - query: 'host.name: linux', - analyze_wildcard: true, - time_zone: 'Zulu', - }, - }, - ], - filter: [], - should: [], - must_not: [], - }, - }, - ], -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts index d3efcaf0f5df5..4386415bc605b 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.test.ts @@ -13,15 +13,11 @@ import { ImportRulesPayloadSchema, ImportRulesSchema, importRulesSchema, - ImportRulesSchemaDecoded, } from './import_rules_schema'; import { getImportRulesSchemaMock, - getImportRulesSchemaDecodedMock, getImportThreatMatchRulesSchemaMock, - getImportThreatMatchRulesSchemaDecodedMock, } from './import_rules_schema.mock'; -import { DEFAULT_MAX_SIGNALS } from '../../../constants'; import { getListArrayMock } from '../types/lists.mock'; describe('import rules schema', () => { @@ -31,14 +27,21 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "description"', - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - 'Invalid value "undefined" supplied to "rule_id"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "rule_id"' + ); expect(message.schema).toEqual({}); }); @@ -63,13 +66,18 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "description"', - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "description"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -82,12 +90,15 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -101,12 +112,15 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -121,12 +135,15 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "name"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "name"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -142,11 +159,12 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "severity"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "severity"' + ); expect(message.schema).toEqual({}); }); @@ -163,10 +181,9 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "risk_score"', - 'Invalid value "undefined" supplied to "type"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "undefined" supplied to "risk_score"' + ); expect(message.schema).toEqual({}); }); @@ -252,34 +269,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['index-1'], - interval: '5m', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { @@ -326,35 +315,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { @@ -378,36 +338,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - query: 'some query', - language: 'kuery', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { @@ -428,33 +358,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { @@ -476,34 +379,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('You can send in an empty array to threat', () => { @@ -516,11 +391,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - threat: [], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threat] does validate', () => { @@ -559,50 +429,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - output_index: '.siem-signals', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - immutable: false, - threat: [ - { - framework: 'someFramework', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - technique: [ - { - id: 'techniqueId', - name: 'techniqueName', - reference: 'techniqueRef', - }, - ], - }, - ], - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('allows references to be sent as valid', () => { @@ -615,11 +441,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - references: ['index-1'], - }; - expect(message.schema).toEqual(expected); }); test('defaults references to an array if it is not sent in', () => { @@ -628,11 +449,6 @@ describe('import rules schema', () => { const checked = exactCheck(noReferences, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - references: [], - }; - expect(message.schema).toEqual(expected); }); test('references cannot be numbers', () => { @@ -671,13 +487,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { interval: expectedInterval, ...expectedNoInterval } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoInterval, - interval: '5m', - }; - expect(message.schema).toEqual(expected); }); test('defaults max signals to 100', () => { @@ -691,18 +500,10 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { max_signals: expectedMaxSignals, ...expectedNoMaxSignals } = - getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoMaxSignals, - max_signals: 100, - }; - expect(message.schema).toEqual(expected); }); test('saved_query type can have filters with it', () => { - const payload: ImportRulesSchema = { + const payload = { ...getImportRulesSchemaMock(), filters: [], }; @@ -711,11 +512,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - filters: [], - }; - expect(message.schema).toEqual(expected); }); test('filters cannot be a string', () => { @@ -734,7 +530,7 @@ describe('import rules schema', () => { }); test('language validates with kuery', () => { - const payload: ImportRulesSchema = { + const payload = { ...getImportRulesSchemaMock(), language: 'kuery', }; @@ -743,15 +539,10 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - language: 'kuery', - }; - expect(message.schema).toEqual(expected); }); test('language validates with lucene', () => { - const payload: ImportRulesSchema = { + const payload = { ...getImportRulesSchemaMock(), language: 'lucene', }; @@ -760,11 +551,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - language: 'lucene', - }; - expect(message.schema).toEqual(expected); }); test('language does not validate with something made up', () => { @@ -820,11 +606,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - max_signals: 1, - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of tags', () => { @@ -837,11 +618,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - tags: ['tag_1', 'tag_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of tags that are numbers', () => { @@ -942,20 +718,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - threat: [ - { - framework: 'fake', - tactic: { - id: 'fakeId', - name: 'fakeName', - reference: 'fakeRef', - }, - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('You can optionally send in an array of false positives', () => { @@ -968,11 +730,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - false_positives: ['false_1', 'false_2'], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of false positives that are numbers', () => { @@ -1014,7 +771,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getImportRulesSchemaDecodedMock()); }); test('You cannot set the immutable to be true', () => { @@ -1083,11 +839,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - risk_score: 0, - }; - expect(message.schema).toEqual(expected); }); test('You can set the risk_score to 100', () => { @@ -1100,11 +851,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - risk_score: 100, - }; - expect(message.schema).toEqual(expected); }); test('You can set meta to any object you want', () => { @@ -1119,13 +865,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - meta: { - somethingMadeUp: { somethingElse: true }, - }, - }; - expect(message.schema).toEqual(expected); }); test('You cannot create meta as a string', () => { @@ -1143,26 +882,6 @@ describe('import rules schema', () => { expect(message.schema).toEqual({}); }); - test('You can omit the query string when filters are present', () => { - const { query, ...noQuery } = getImportRulesSchemaMock(); - const payload: ImportRulesSchema = { - ...noQuery, - filters: [], - }; - - const decoded = importRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([]); - - const { query: expectedQuery, ...expectedNoQuery } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoQuery, - filters: [], - }; - expect(message.schema).toEqual(expected); - }); - test('validates with timeline_id and timeline_title', () => { const payload: ImportRulesSchema = { ...getImportRulesSchemaMock(), @@ -1174,12 +893,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - timeline_id: 'timeline-id', - timeline_title: 'timeline-title', - }; - expect(message.schema).toEqual(expected); }); test('rule_id is required and you cannot get by with just id', () => { @@ -1212,14 +925,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - created_at: '2020-01-09T06:15:24.749Z', - updated_at: '2020-01-09T06:15:24.749Z', - created_by: 'Braden Hassanabad', - updated_by: 'Evan Hassanabad', - }; - expect(message.schema).toEqual(expected); }); test('it does not validate with epoch strings for created_at', () => { @@ -1300,13 +1005,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { from: expectedFrom, ...expectedNoFrom } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoFrom, - from: 'now-6m', - }; - expect(message.schema).toEqual(expected); }); test('The default for "to" will be "now"', () => { @@ -1319,13 +1017,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { to: expectedTo, ...expectedNoTo } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoTo, - to: 'now', - }; - expect(message.schema).toEqual(expected); }); test('You cannot set the severity to a value other than low, medium, high, or critical', () => { @@ -1351,13 +1042,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { actions: expectedActions, ...expectedNoActions } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoActions, - actions: [], - }; - expect(message.schema).toEqual(expected); }); test('You cannot send in an array of actions that are missing "group"', () => { const payload: Omit<ImportRulesSchema['actions'], 'group'> = { @@ -1451,13 +1135,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - - const { throttle: expectedThrottle, ...expectedNoThrottle } = getImportRulesSchemaDecodedMock(); - const expected: ImportRulesSchemaDecoded = { - ...expectedNoThrottle, - throttle: null, - }; - expect(message.schema).toEqual(expected); }); describe('note', () => { @@ -1471,11 +1148,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - note: '# documentation markdown here', - }; - expect(message.schema).toEqual(expected); }); test('You can set note to an empty string', () => { @@ -1488,11 +1160,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - ...getImportRulesSchemaDecodedMock(), - note: '', - }; - expect(message.schema).toEqual(expected); }); test('You cannot create note as an object', () => { @@ -1531,34 +1198,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - immutable: false, - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1584,48 +1223,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - filters: [], - immutable: false, - exceptions_list: [ - { - id: 'some_uuid', - list_id: 'list_id_single', - namespace_type: 'single', - type: 'detection', - }, - { - id: 'endpoint_list', - list_id: 'endpoint_list', - namespace_type: 'agnostic', - type: 'endpoint', - }, - ], - }; - expect(message.schema).toEqual(expected); }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, note, and empty exceptions_list] does validate', () => { @@ -1649,41 +1246,10 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - immutable: false, - filters: [], - exceptions_list: [], - }; - expect(message.schema).toEqual(expected); }); test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and invalid exceptions_list] does NOT validate', () => { - const payload: Omit<ImportRulesSchema, 'exceptions_list'> & { - exceptions_list: Array<{ id: string; namespace_type: string }>; - } = { + const payload = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1730,35 +1296,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - description: 'some description', - from: 'now-5m', - to: 'now', - index: ['index-1'], - name: 'some-name', - severity: 'low', - interval: '5m', - type: 'query', - risk_score: 50, - note: '# some markdown', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - immutable: false, - exceptions_list: [], - filters: [], - }; - expect(message.schema).toEqual(expected); }); }); @@ -1768,9 +1305,7 @@ describe('import rules schema', () => { const decoded = importRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - const expected = getImportThreatMatchRulesSchemaDecodedMock(); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); }); }); @@ -1795,35 +1330,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: [], - data_view_id: 'logs-*', - interval: '5m', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); // Both can be defined, but if a data_view_id is defined, rule will use that one @@ -1847,35 +1353,6 @@ describe('import rules schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: ImportRulesSchemaDecoded = { - author: [], - severity_mapping: [], - risk_score_mapping: [], - rule_id: 'rule-1', - risk_score: 50, - description: 'some description', - from: 'now-5m', - to: 'now', - name: 'some-name', - severity: 'low', - type: 'query', - query: 'some query', - index: ['auditbeat-*'], - data_view_id: 'logs-*', - interval: '5m', - references: [], - actions: [], - enabled: true, - false_positives: [], - max_signals: DEFAULT_MAX_SIGNALS, - tags: [], - threat: [], - throttle: null, - version: 1, - exceptions_list: [], - immutable: false, - }; - expect(message.schema).toEqual(expected); }); test('data_view_id cannot be a number', () => { diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts index 6e419e97775e7..b3d533a167a7a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts @@ -7,86 +7,19 @@ import * as t from 'io-ts'; +import { OnlyFalseAllowed } from '@kbn/securitysolution-io-ts-types'; import { - Actions, - DefaultActionsArray, - DefaultFromString, - DefaultIntervalString, - DefaultMaxSignalsNumber, - DefaultRiskScoreMappingArray, - DefaultSeverityMappingArray, - DefaultThreatArray, - DefaultThrottleNull, - DefaultToString, - From, - machine_learning_job_id, - risk_score, - RiskScoreMapping, - threat_index, - items_per_search, - concurrent_searches, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - Threats, - type, - language, - severity, - SeverityMapping, - ThrottleOrNull, - MaxSignals, -} from '@kbn/securitysolution-io-ts-alerting-types'; - -import { - DefaultVersionNumber, - Version, - DefaultStringArray, - DefaultBooleanTrue, - OnlyFalseAllowed, -} from '@kbn/securitysolution-io-ts-types'; -import { DefaultListArray, ListArray } from '@kbn/securitysolution-io-ts-list-types'; -import { - description, - anomaly_threshold, - filters, - RuleId, - index, - data_view_id, - output_index, - saved_id, - timeline_id, - timeline_title, - meta, - name, - Tags, - To, - threshold, - note, - References, - Enabled, - FalsePositives, - Interval, - query, rule_id, id, created_at, updated_at, created_by, updated_by, - building_block_type, - license, - rule_name_override, - timestamp_override, - Author, - timestamp_field, - event_category_override, - tiebreaker_field, RelatedIntegrationArray, RequiredFieldArray, SetupGuide, } from '../common'; +import { baseCreateParams, createTypeSpecific } from './rule_schemas'; /** * Differences from this and the createRulesSchema are @@ -99,121 +32,26 @@ import { * - updated_by is optional (but ignored in the import code) */ export const importRulesSchema = t.intersection([ - t.exact( - t.type({ - description, - risk_score, - name, - severity, - type, - rule_id, - }) - ), + baseCreateParams, + createTypeSpecific, + t.exact(t.type({ rule_id })), t.exact( t.partial({ - id, // defaults to undefined if not set during decode - actions: DefaultActionsArray, // defaults to empty actions array if not set during decode - anomaly_threshold, // defaults to undefined if not set during decode - author: DefaultStringArray, // defaults to empty array of strings if not set during decode - building_block_type, // defaults to undefined if not set during decode - enabled: DefaultBooleanTrue, // defaults to true if not set during decode - timestamp_field, // defaults to "undefined" if not set during decode - event_category_override, // defaults to "undefined" if not set during decode - tiebreaker_field, // defaults to "undefined" if not set during decode - false_positives: DefaultStringArray, // defaults to empty string array if not set during decode - filters, // defaults to undefined if not set during decode - from: DefaultFromString, // defaults to "now-6m" if not set during decode - index, // defaults to undefined if not set during decode - data_view_id, // defaults to undefined if not set during decode - immutable: OnlyFalseAllowed, // defaults to "false" if not set during decode - interval: DefaultIntervalString, // defaults to "5m" if not set during decode - query, // defaults to undefined if not set during decode - language, // defaults to undefined if not set during decode - license, // defaults to "undefined" if not set during decode - // TODO: output_index: This should be removed eventually - output_index, // defaults to "undefined" if not set during decode - saved_id, // defaults to "undefined" if not set during decode - timeline_id, // defaults to "undefined" if not set during decode - timeline_title, // defaults to "undefined" if not set during decode - meta, // defaults to "undefined" if not set during decode - machine_learning_job_id, // defaults to "undefined" if not set during decode - max_signals: DefaultMaxSignalsNumber, // defaults to DEFAULT_MAX_SIGNALS (100) if not set during decode - related_integrations: RelatedIntegrationArray, // defaults to "undefined" if not set during decode - required_fields: RequiredFieldArray, // defaults to "undefined" if not set during decode - risk_score_mapping: DefaultRiskScoreMappingArray, // defaults to empty risk score mapping array if not set during decode - rule_name_override, // defaults to "undefined" if not set during decode - setup: SetupGuide, // defaults to "undefined" if not set during decode - severity_mapping: DefaultSeverityMappingArray, // defaults to empty actions array if not set during decode - tags: DefaultStringArray, // defaults to empty string array if not set during decode - to: DefaultToString, // defaults to "now" if not set during decode - threat: DefaultThreatArray, // defaults to empty array if not set during decode - threshold, // defaults to "undefined" if not set during decode - throttle: DefaultThrottleNull, // defaults to "null" if not set during decode - timestamp_override, // defaults to "undefined" if not set during decode - references: DefaultStringArray, // defaults to empty array of strings if not set during decode - note, // defaults to "undefined" if not set during decode - version: DefaultVersionNumber, // defaults to 1 if not set during decode - exceptions_list: DefaultListArray, // defaults to empty array if not set during decode - created_at, // defaults "undefined" if not set during decode - updated_at, // defaults "undefined" if not set during decode - created_by, // defaults "undefined" if not set during decode - updated_by, // defaults "undefined" if not set during decode - threat_filters, // defaults to "undefined" if not set during decode - threat_mapping, // defaults to "undefined" if not set during decode - threat_query, // defaults to "undefined" if not set during decode - threat_index, // defaults to "undefined" if not set during decode - threat_language, // defaults "undefined" if not set during decode - threat_indicator_path, // defaults to "undefined" if not set during decode - concurrent_searches, // defaults to "undefined" if not set during decode - items_per_search, // defaults to "undefined" if not set during decode + id, + immutable: OnlyFalseAllowed, + updated_at, + updated_by, + created_at, + created_by, + related_integrations: RelatedIntegrationArray, + required_fields: RequiredFieldArray, + setup: SetupGuide, }) ), ]); export type ImportRulesSchema = t.TypeOf<typeof importRulesSchema>; -// This type is used after a decode since some things are defaults after a decode. -export type ImportRulesSchemaDecoded = Omit< - ImportRulesSchema, - | 'author' - | 'references' - | 'actions' - | 'enabled' - | 'false_positives' - | 'from' - | 'interval' - | 'max_signals' - | 'risk_score_mapping' - | 'severity_mapping' - | 'tags' - | 'to' - | 'threat' - | 'throttle' - | 'version' - | 'exceptions_list' - | 'rule_id' - | 'immutable' -> & { - author: Author; - references: References; - actions: Actions; - enabled: Enabled; - false_positives: FalsePositives; - from: From; - interval: Interval; - max_signals: MaxSignals; - risk_score_mapping: RiskScoreMapping; - severity_mapping: SeverityMapping; - tags: Tags; - to: To; - threat: Threats; - throttle: ThrottleOrNull; - version: Version; - exceptions_list: ListArray; - rule_id: RuleId; - immutable: false; -}; - export const importRulesPayloadSchema = t.exact( t.type({ file: t.object, @@ -221,5 +59,3 @@ export const importRulesPayloadSchema = t.exact( ); export type ImportRulesPayloadSchema = t.TypeOf<typeof importRulesPayloadSchema>; - -export type ImportRulesPayloadSchemaDecoded = ImportRulesPayloadSchema; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts index c3f4ca78e3b8d..211f649a218e4 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.test.ts @@ -10,23 +10,6 @@ import { ImportRulesSchema } from './import_rules_schema'; import { importRuleValidateTypeDependents } from './import_rules_type_dependents'; describe('import_rules_type_dependents', () => { - test('saved_id is required when type is saved_query and will not validate without out', () => { - const schema: ImportRulesSchema = { ...getImportRulesSchemaMock(), type: 'saved_query' }; - delete schema.saved_id; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "saved_query", "saved_id" is required']); - }); - - test('saved_id is required when type is saved_query and validates with it', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - type: 'saved_query', - saved_id: '123', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - test('You cannot omit timeline_title when timeline_id is present', () => { const schema: ImportRulesSchema = { ...getImportRulesSchemaMock(), @@ -66,13 +49,4 @@ describe('import_rules_type_dependents', () => { const errors = importRuleValidateTypeDependents(schema); expect(errors).toEqual(['when "timeline_title" exists, "timeline_id" must also exist']); }); - - test('threshold is required when type is threshold and validates with it', () => { - const schema: ImportRulesSchema = { - ...getImportRulesSchemaMock(), - type: 'threshold', - }; - const errors = importRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']); - }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts index 9bc90e63238e7..1b55be7bfda98 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_type_dependents.ts @@ -5,70 +5,8 @@ * 2.0. */ -import { isMlRule } from '../../../machine_learning/helpers'; -import { isThresholdRule } from '../../utils'; import { ImportRulesSchema } from './import_rules_schema'; -export const validateAnomalyThreshold = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.anomaly_threshold == null) { - return ['when "type" is "machine_learning" anomaly_threshold is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateQuery = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.query != null) { - return ['when "type" is "machine_learning", "query" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateLanguage = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.language != null) { - return ['when "type" is "machine_learning", "language" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateSavedId = (rule: ImportRulesSchema): string[] => { - if (rule.type === 'saved_query') { - if (rule.saved_id == null) { - return ['when "type" is "saved_query", "saved_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateMachineLearningJobId = (rule: ImportRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.machine_learning_job_id == null) { - return ['when "type" is "machine_learning", "machine_learning_job_id" is required']; - } else { - return []; - } - } else { - return []; - } -}; - export const validateTimelineId = (rule: ImportRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { @@ -97,33 +35,20 @@ export const validateTimelineTitle = (rule: ImportRulesSchema): string[] => { export const validateThreshold = (rule: ImportRulesSchema): string[] => { const errors: string[] = []; - if (isThresholdRule(rule.type)) { - if (!rule.threshold) { - errors.push('when "type" is "threshold", "threshold" is required'); - } else { - if ( - rule.threshold.cardinality?.length && - rule.threshold.field.includes(rule.threshold.cardinality[0].field) - ) { - errors.push('Cardinality of a field that is being aggregated on is always 1'); - } - if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { - errors.push('Number of fields must be 3 or less'); - } + if (rule.type === 'threshold') { + if ( + rule.threshold.cardinality?.length && + rule.threshold.field.includes(rule.threshold.cardinality[0].field) + ) { + errors.push('Cardinality of a field that is being aggregated on is always 1'); + } + if (Array.isArray(rule.threshold.field) && rule.threshold.field.length > 3) { + errors.push('Number of fields must be 3 or less'); } } return errors; }; export const importRuleValidateTypeDependents = (rule: ImportRulesSchema): string[] => { - return [ - ...validateAnomalyThreshold(rule), - ...validateQuery(rule), - ...validateLanguage(rule), - ...validateSavedId(rule), - ...validateMachineLearningJobId(rule), - ...validateTimelineId(rule), - ...validateTimelineTitle(rule), - ...validateThreshold(rule), - ]; + return [...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule)]; }; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts index 290f190562fe2..84a3607310caf 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.test.ts @@ -23,16 +23,6 @@ describe('patch_rules_bulk_schema', () => { expect(output.schema).toEqual([]); }); - test('made up values do not validate for a single element', () => { - const payload: Array<{ madeUp: string }> = [{ madeUp: 'hi' }]; - - const decoded = patchRulesBulkSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const output = foldLeftRight(checked); - expect(formatErrors(output.errors)).toEqual(['invalid keys "madeUp"']); - expect(output.schema).toEqual({}); - }); - test('single array of [id] does validate', () => { const payload: PatchRulesBulkSchema = [{ id: '4125761e-51da-4de9-a0c8-42824f532ddb' }]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts index 3b6919710c3fc..0472c62228c4d 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_bulk_schema.ts @@ -7,9 +7,7 @@ import * as t from 'io-ts'; -import { patchRulesSchema, PatchRulesSchemaDecoded } from './patch_rules_schema'; +import { patchRulesSchema } from './patch_rules_schema'; export const patchRulesBulkSchema = t.array(patchRulesSchema); export type PatchRulesBulkSchema = t.TypeOf<typeof patchRulesBulkSchema>; - -export type PatchRulesBulkSchemaDecoded = PatchRulesSchemaDecoded[]; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts index c476a8e46a621..2581b6098eff5 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.mock.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { PatchRulesSchema, PatchRulesSchemaDecoded } from './patch_rules_schema'; +import { PatchRulesSchema, ThresholdPatchSchema } from './patch_rules_schema'; export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ description: 'some description', @@ -18,5 +18,17 @@ export const getPatchRulesSchemaMock = (): PatchRulesSchema => ({ rule_id: 'rule-1', }); -export const getPatchRulesSchemaDecodedMock = (): PatchRulesSchemaDecoded => - getPatchRulesSchemaMock(); +export const getPatchThresholdRulesSchemaMock = (): ThresholdPatchSchema => ({ + description: 'some description', + name: 'Query with a rule id', + query: 'user.name: root or user.name: admin', + severity: 'high', + type: 'threshold', + risk_score: 55, + language: 'kuery', + rule_id: 'rule-1', + threshold: { + field: 'host.name', + value: 10, + }, +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts index 5ac49deb32e0d..9352d1e008384 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.test.ts @@ -5,27 +5,14 @@ * 2.0. */ -import { patchRulesSchema, PatchRulesSchema, PatchRulesSchemaDecoded } from './patch_rules_schema'; -import { getPatchRulesSchemaMock, getPatchRulesSchemaDecodedMock } from './patch_rules_schema.mock'; +import { patchRulesSchema, PatchRulesSchema } from './patch_rules_schema'; +import { getPatchRulesSchemaMock } from './patch_rules_schema.mock'; import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils'; import { pipe } from 'fp-ts/lib/pipeable'; import { left } from 'fp-ts/lib/Either'; import { getListArrayMock } from '../types/lists.mock'; describe('patch_rules_schema', () => { - test('made up values do not validate', () => { - const payload: PatchRulesSchema & { madeUp: string } = { - ...getPatchRulesSchemaMock(), - madeUp: 'hi', - }; - - const decoded = patchRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['invalid keys "madeUp"']); - expect(message.schema).toEqual({}); - }); - test('[id] does validate', () => { const payload: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', @@ -35,7 +22,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', }; expect(message.schema).toEqual(expected); @@ -50,7 +37,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', }; expect(message.schema).toEqual(expected); @@ -66,7 +53,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', }; @@ -83,7 +70,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', }; @@ -100,7 +87,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', risk_score: 10, }; @@ -118,7 +105,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -138,7 +125,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -159,7 +146,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -181,7 +168,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -204,7 +191,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -228,7 +215,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -253,7 +240,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -279,7 +266,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -306,7 +293,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -334,7 +321,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -363,7 +350,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -394,7 +381,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -427,7 +414,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -455,7 +442,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -482,7 +469,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -513,7 +500,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -547,7 +534,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', description: 'some description', from: 'now-5m', @@ -573,7 +560,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).references).toEqual(undefined); + expect((message.schema as PatchRulesSchema).references).toEqual(undefined); }); test('does not default interval', () => { @@ -585,7 +572,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).interval).toEqual(undefined); + expect((message.schema as PatchRulesSchema).interval).toEqual(undefined); }); test('does not default max_signals', () => { @@ -597,7 +584,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).max_signals).toEqual(undefined); + expect((message.schema as PatchRulesSchema).max_signals).toEqual(undefined); }); test('references cannot be numbers', () => { @@ -616,13 +603,17 @@ describe('patch_rules_schema', () => { test('indexes cannot be numbers', () => { const payload: Omit<PatchRulesSchema, 'index'> & { index: number[] } = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', + type: 'query', index: [5], }; const decoded = patchRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to "index"']); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "query" supplied to "type"', + 'Invalid value "5" supplied to "index"', + ]); expect(message.schema).toEqual({}); }); @@ -636,7 +627,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { id: 'b8f95e17-681f-407f-8a5e-b832a77d3831', type: 'saved_query', }; @@ -644,7 +635,7 @@ describe('patch_rules_schema', () => { }); test('saved_id validates with type:saved_query', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), type: 'saved_query', saved_id: 'some id', @@ -654,8 +645,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), type: 'saved_query', saved_id: 'some id', }; @@ -663,7 +654,7 @@ describe('patch_rules_schema', () => { }); test('saved_query type can have filters with it', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), saved_id: 'some id', filters: [], @@ -673,8 +664,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), saved_id: 'some id', filters: [], }; @@ -682,7 +673,7 @@ describe('patch_rules_schema', () => { }); test('language validates with kuery', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', language: 'kuery', @@ -692,8 +683,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), query: 'some query', language: 'kuery', }; @@ -701,7 +692,7 @@ describe('patch_rules_schema', () => { }); test('language validates with lucene', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', language: 'lucene', @@ -712,8 +703,8 @@ describe('patch_rules_schema', () => { const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), query: 'some query', language: 'lucene', }; @@ -721,7 +712,7 @@ describe('patch_rules_schema', () => { }); test('language does not validate with something made up', () => { - const payload: Omit<PatchRulesSchema, 'language'> & { language: string } = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', language: 'something-made-up', @@ -730,14 +721,14 @@ describe('patch_rules_schema', () => { const decoded = patchRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "something-made-up" supplied to "language"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "something-made-up" supplied to "language"' + ); expect(message.schema).toEqual({}); }); test('max_signals cannot be negative', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', max_signals: -1, @@ -753,7 +744,7 @@ describe('patch_rules_schema', () => { }); test('max_signals cannot be zero', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', max_signals: 0, @@ -767,7 +758,7 @@ describe('patch_rules_schema', () => { }); test('max_signals can be 1', () => { - const payload: PatchRulesSchema = { + const payload = { ...getPatchRulesSchemaMock(), query: 'some query', max_signals: 1, @@ -777,8 +768,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected = { + ...getPatchRulesSchemaMock(), query: 'some query', max_signals: 1, }; @@ -795,8 +786,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected: PatchRulesSchema = { + ...getPatchRulesSchemaMock(), meta: { whateverYouWant: 'anything_at_all' }, }; expect(message.schema).toEqual(expected); @@ -826,9 +817,9 @@ describe('patch_rules_schema', () => { const decoded = patchRulesSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "should not work" supplied to "filters"', - ]); + expect(getPaths(left(message.errors))).toContain( + 'Invalid value "should not work" supplied to "filters"' + ); expect(message.schema).toEqual({}); }); @@ -867,7 +858,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).threat).toEqual(undefined); + expect((message.schema as PatchRulesSchema).threat).toEqual(undefined); }); test('threat is not defaulted to undefined on patch with empty array', () => { @@ -880,7 +871,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect((message.schema as PatchRulesSchemaDecoded).threat).toEqual([]); + expect((message.schema as PatchRulesSchema).threat).toEqual([]); }); test('threat is valid when updated with all sub-objects', () => { @@ -1007,8 +998,8 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { - ...getPatchRulesSchemaDecodedMock(), + const expected: PatchRulesSchema = { + ...getPatchRulesSchemaMock(), timeline_id: 'some-id', timeline_title: 'some-title', }; @@ -1047,7 +1038,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1072,7 +1063,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', note: '# new documentation markdown', }; @@ -1185,7 +1176,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1236,7 +1227,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1255,9 +1246,7 @@ describe('patch_rules_schema', () => { }); test('rule_id, description, from, to, index, name, severity, interval, type, filters, risk_score, note, and invalid exceptions_list] does NOT validate', () => { - const payload: Omit<PatchRulesSchema, 'exceptions_list'> & { - exceptions_list: Array<{ id: string; namespace_type: string }>; - } = { + const payload = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -1280,7 +1269,6 @@ describe('patch_rules_schema', () => { 'Invalid value "undefined" supplied to "exceptions_list,list_id"', 'Invalid value "undefined" supplied to "exceptions_list,type"', 'Invalid value "not a namespace type" supplied to "exceptions_list,namespace_type"', - 'Invalid value "[{"id":"uuid_here","namespace_type":"not a namespace type"}]" supplied to "exceptions_list"', ]); expect(message.schema).toEqual({}); }); @@ -1305,7 +1293,7 @@ describe('patch_rules_schema', () => { const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - const expected: PatchRulesSchemaDecoded = { + const expected: PatchRulesSchema = { rule_id: 'rule-1', description: 'some description', from: 'now-5m', diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts index 80a267ab6c2da..300a31b0d4b55 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts @@ -8,126 +8,36 @@ import * as t from 'io-ts'; import { - actions, - from, - machine_learning_job_id, - risk_score, - risk_score_mapping, - threat_index, - concurrent_searches, - items_per_search, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - threats, - type, - language, - severity, - severity_mapping, - max_signals, - throttle, -} from '@kbn/securitysolution-io-ts-alerting-types'; - -import { version } from '@kbn/securitysolution-io-ts-types'; - -import { listArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; -import { - description, - anomaly_threshold, - filters, - index, - data_view_id, - output_index, - saved_id, - timeline_id, - timeline_title, - meta, - rule_id, - name, - note, - false_positives, - interval, - enabled, - tags, - threshold, - references, - to, - query, - id, - building_block_type, - author, - license, - rule_name_override, - timestamp_override, - timestamp_field, - event_category_override, - tiebreaker_field, -} from '../common'; + patchTypeSpecific, + sharedPatchSchema, + eqlPatchParams, + threatMatchPatchParams, + queryPatchParams, + savedQueryPatchParams, + thresholdPatchParams, + machineLearningPatchParams, +} from './rule_schemas'; /** * All of the patch elements should default to undefined if not set */ -export const patchRulesSchema = t.exact( - t.partial({ - author, - building_block_type, - description, - risk_score, - name, - severity, - type, - id, - actions, - anomaly_threshold, - enabled, - timestamp_field, - event_category_override, - tiebreaker_field, - false_positives, - filters, - from, - rule_id, - index, - data_view_id, - interval, - query, - language, - license, - // TODO: output_index: This should be removed eventually - output_index, - saved_id, - timeline_id, - timeline_title, - meta, - machine_learning_job_id, - max_signals, - risk_score_mapping, - rule_name_override, - severity_mapping, - tags, - to, - threat: threats, - threshold, - throttle, - timestamp_override, - references, - note, - version, - exceptions_list: listArrayOrUndefined, - threat_index, - threat_query, - threat_filters, - threat_mapping, - threat_language, - threat_indicator_path, - concurrent_searches, - items_per_search, - }) -); - +export const patchRulesSchema = t.intersection([patchTypeSpecific, sharedPatchSchema]); export type PatchRulesSchema = t.TypeOf<typeof patchRulesSchema>; -// This type is used after a decode since some things are defaults after a decode. -export type PatchRulesSchemaDecoded = PatchRulesSchema; +const eqlPatchSchema = t.intersection([eqlPatchParams, sharedPatchSchema]); +export type EqlPatchSchema = t.TypeOf<typeof eqlPatchSchema>; + +const threatMatchPatchSchema = t.intersection([threatMatchPatchParams, sharedPatchSchema]); +export type ThreatMatchPatchSchema = t.TypeOf<typeof threatMatchPatchSchema>; + +const queryPatchSchema = t.intersection([queryPatchParams, sharedPatchSchema]); +export type QueryPatchSchema = t.TypeOf<typeof queryPatchSchema>; + +const savedQueryPatchSchema = t.intersection([savedQueryPatchParams, sharedPatchSchema]); +export type SavedQueryPatchSchema = t.TypeOf<typeof savedQueryPatchSchema>; + +const thresholdPatchSchema = t.intersection([thresholdPatchParams, sharedPatchSchema]); +export type ThresholdPatchSchema = t.TypeOf<typeof thresholdPatchSchema>; + +const machineLearningPatchSchema = t.intersection([machineLearningPatchParams, sharedPatchSchema]); +export type MachineLearningPatchSchema = t.TypeOf<typeof machineLearningPatchSchema>; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts index 0c4783acd0705..3e81ad62bf335 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.test.ts @@ -5,21 +5,14 @@ * 2.0. */ -import { getPatchRulesSchemaMock } from './patch_rules_schema.mock'; -import { PatchRulesSchema } from './patch_rules_schema'; +import { + getPatchRulesSchemaMock, + getPatchThresholdRulesSchemaMock, +} from './patch_rules_schema.mock'; +import { PatchRulesSchema, ThresholdPatchSchema } from './patch_rules_schema'; import { patchRuleValidateTypeDependents } from './patch_rules_type_dependents'; describe('patch_rules_type_dependents', () => { - test('saved_id is required when type is saved_query and validates with it', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'saved_query', - saved_id: '123', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual([]); - }); - test('You cannot omit timeline_title when timeline_id is present', () => { const schema: PatchRulesSchema = { ...getPatchRulesSchemaMock(), @@ -80,19 +73,9 @@ describe('patch_rules_type_dependents', () => { expect(errors).toEqual(['either "id" or "rule_id" must be set']); }); - test('threshold is required when type is threshold and validates with it', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', - }; - const errors = patchRuleValidateTypeDependents(schema); - expect(errors).toEqual(['when "type" is "threshold", "threshold" is required']); - }); - test('threshold.value is required and has to be bigger than 0 when type is threshold and validates with it', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', + const schema: ThresholdPatchSchema = { + ...getPatchThresholdRulesSchemaMock(), threshold: { field: '', value: -1, @@ -103,9 +86,8 @@ describe('patch_rules_type_dependents', () => { }); test('threshold.field should contain 3 items or less', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', + const schema: ThresholdPatchSchema = { + ...getPatchThresholdRulesSchemaMock(), threshold: { field: ['field-1', 'field-2', 'field-3', 'field-4'], value: 1, @@ -116,9 +98,8 @@ describe('patch_rules_type_dependents', () => { }); test('threshold.cardinality[0].field should not be in threshold.field', () => { - const schema: PatchRulesSchema = { - ...getPatchRulesSchemaMock(), - type: 'threshold', + const schema: ThresholdPatchSchema = { + ...getPatchThresholdRulesSchemaMock(), threshold: { field: ['field-1', 'field-2', 'field-3'], value: 1, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts index 7229b403c92ad..cdba19f45ca2a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_type_dependents.ts @@ -5,33 +5,8 @@ * 2.0. */ -import { isMlRule } from '../../../machine_learning/helpers'; import { PatchRulesSchema } from './patch_rules_schema'; -export const validateQuery = (rule: PatchRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.query != null) { - return ['when "type" is "machine_learning", "query" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - -export const validateLanguage = (rule: PatchRulesSchema): string[] => { - if (isMlRule(rule.type)) { - if (rule.language != null) { - return ['when "type" is "machine_learning", "language" cannot be set']; - } else { - return []; - } - } else { - return []; - } -}; - export const validateTimelineId = (rule: PatchRulesSchema): string[] => { if (rule.timeline_id != null) { if (rule.timeline_title == null) { @@ -94,8 +69,6 @@ export const validateThreshold = (rule: PatchRulesSchema): string[] => { export const patchRuleValidateTypeDependents = (rule: PatchRulesSchema): string[] => { return [ ...validateId(rule), - ...validateQuery(rule), - ...validateLanguage(rule), ...validateTimelineId(rule), ...validateTimelineTitle(rule), ...validateThreshold(rule), diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index 33127dc434d82..8d72861047640 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -101,9 +101,9 @@ const patchSchema = < defaultableFields: Defaultable ) => { return t.intersection([ - t.exact(t.partial(requiredFields)), - t.exact(t.partial(optionalFields)), - t.exact(t.partial(defaultableFields)), + t.partial(requiredFields), + t.partial(optionalFields), + t.partial(defaultableFields), ]); }; @@ -190,6 +190,7 @@ const { patch: basePatchParams, response: baseResponseParams, } = buildAPISchemas(baseParams); +export { baseCreateParams }; // "shared" types are the same across all rule types, and built from "baseParams" above // with some variations for each route. These intersect with type specific schemas below @@ -207,6 +208,13 @@ export const sharedUpdateSchema = t.intersection([ ]); export type SharedUpdateSchema = t.TypeOf<typeof sharedUpdateSchema>; +export const sharedPatchSchema = t.intersection([ + basePatchParams, + t.exact(t.partial({ rule_id, id })), +]); + +// START type specific parameter definitions +// ----------------------------------------- const eqlRuleParams = { required: { type: t.literal('eql'), @@ -348,8 +356,10 @@ const { } = buildAPISchemas(machineLearningRuleParams); export { machineLearningCreateParams }; +// --------------------------------------- +// END type specific parameter definitions -const createTypeSpecific = t.union([ +export const createTypeSpecific = t.union([ eqlCreateParams, threatMatchCreateParams, queryCreateParams, @@ -389,7 +399,7 @@ export type MachineLearningUpdateSchema = UpdateSchema< t.TypeOf<typeof machineLearningCreateParams> >; -const patchTypeSpecific = t.union([ +export const patchTypeSpecific = t.union([ eqlPatchParams, threatMatchPatchParams, queryPatchParams, @@ -397,6 +407,22 @@ const patchTypeSpecific = t.union([ thresholdPatchParams, machineLearningPatchParams, ]); +export { + eqlPatchParams, + threatMatchPatchParams, + queryPatchParams, + savedQueryPatchParams, + thresholdPatchParams, + machineLearningPatchParams, +}; +export type PatchTypeSpecific = t.TypeOf<typeof patchTypeSpecific>; + +export type EqlPatchParams = t.TypeOf<typeof eqlPatchParams>; +export type ThreatMatchPatchParams = t.TypeOf<typeof threatMatchPatchParams>; +export type QueryPatchParams = t.TypeOf<typeof queryPatchParams>; +export type SavedQueryPatchParams = t.TypeOf<typeof savedQueryPatchParams>; +export type ThresholdPatchParams = t.TypeOf<typeof thresholdPatchParams>; +export type MachineLearningPatchParams = t.TypeOf<typeof machineLearningPatchParams>; const responseTypeSpecific = t.union([ eqlResponseParams, @@ -411,11 +437,24 @@ export type ResponseTypeSpecific = t.TypeOf<typeof responseTypeSpecific>; export const updateRulesSchema = t.intersection([createTypeSpecific, sharedUpdateSchema]); export type UpdateRulesSchema = t.TypeOf<typeof updateRulesSchema>; -export const fullPatchSchema = t.intersection([ - basePatchParams, - patchTypeSpecific, - t.exact(t.partial({ id })), +export const eqlFullPatchSchema = t.intersection([eqlPatchParams, sharedPatchSchema]); +export type EqlFullPatchSchema = t.TypeOf<typeof eqlFullPatchSchema>; +export const threatMatchFullPatchSchema = t.intersection([ + threatMatchPatchParams, + sharedPatchSchema, +]); +export type ThreatMatchFullPatchSchema = t.TypeOf<typeof threatMatchFullPatchSchema>; +export const queryFullPatchSchema = t.intersection([queryPatchParams, sharedPatchSchema]); +export type QueryFullPatchSchema = t.TypeOf<typeof queryFullPatchSchema>; +export const savedQueryFullPatchSchema = t.intersection([savedQueryPatchParams, sharedPatchSchema]); +export type SavedQueryFullPatchSchema = t.TypeOf<typeof savedQueryFullPatchSchema>; +export const thresholdFullPatchSchema = t.intersection([thresholdPatchParams, sharedPatchSchema]); +export type ThresholdFullPatchSchema = t.TypeOf<typeof thresholdFullPatchSchema>; +export const machineLearningFullPatchSchema = t.intersection([ + machineLearningPatchParams, + sharedPatchSchema, ]); +export type MachineLearningFullPatchSchema = t.TypeOf<typeof machineLearningFullPatchSchema>; const responseRequiredFields = { id, diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts index 1e532e29fcc3e..ca663e8acbc9a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts @@ -129,7 +129,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial<Rul interval: '5m', index: ['auditbeat-*'], rule_id: 'rule-1', - output_index: '.siem-signals-default', + output_index: '', max_signals: 100, risk_score: 55, risk_score_mapping: [], 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 30d75b30a11b6..4b6722ba8d452 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 @@ -192,14 +192,6 @@ export async function indexEndpointHostDocs({ await indexEndpointAndFleetActionsForHost(client, hostMetadata, undefined); } - hostMetadata = { - ...hostMetadata, - // since the united transform uses latest metadata transform as a source - // there is an extra delay and fleet-agents gets populated much sooner. - // we manually add a delay to the time sync field so that the united transform - // will pick up the latest metadata doc. - '@timestamp': hostMetadata['@timestamp'] + 60000, - }; await client .index({ index: metadataIndex, diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 35eb9de6d4060..c817c53b7658f 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -1769,6 +1769,7 @@ export class EndpointDocGenerator extends BaseDataGenerator { install_started_at: '2020-06-24T14:41:23.098Z', install_source: 'registry', keep_policies_up_to_date: false, + verification_status: 'unknown', }, references: [], updated_at: '2020-06-24T14:41:23.098Z', diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index 24404ce1637e5..c89d12c9a2efa 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -218,7 +218,7 @@ export type HostIsolationRequestBody = TypeOf<typeof NoParametersRequestSchema.b export type ResponseActionRequestBody = TypeOf<typeof ResponseActionBodySchema>; -export type KillProcessRequestBody = TypeOf<typeof KillOrSuspendProcessRequestSchema.body>; +export type KillOrSuspendProcessRequestBody = TypeOf<typeof KillOrSuspendProcessRequestSchema.body>; export interface HostIsolationResponse { action: string; diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 8ba1a611af2b6..f22a0b4af5170 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -38,6 +38,10 @@ export const allowedExperimentalValues = Object.freeze({ * Enables the Endpoint response actions console in various areas of the app */ responseActionsConsoleEnabled: false, + /** + * Enables the cloud security posture navigation inside the security solution + */ + cloudSecurityPostureNavigation: false, }); type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts similarity index 55% rename from x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts rename to x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts index 3281fabe30829..1ee05066d8ae7 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/first_last_seen/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/first_last_seen/index.ts @@ -6,17 +6,18 @@ */ import type { IEsSearchResponse } from '@kbn/data-plugin/common'; -import { Inspect, Maybe, Direction } from '../../../common'; -import { RequestOptionsPaginated } from '../..'; -import { HostsFields } from '../common'; -export interface HostFirstLastSeenRequestOptions - extends Partial<RequestOptionsPaginated<HostsFields>> { - hostName: string; +import { Inspect, Maybe, Direction } from '../../common'; +import { RequestBasicOptions } from '../..'; + +export const FirstLastSeenQuery = 'firstlastseen'; +export interface FirstLastSeenRequestOptions extends Partial<RequestBasicOptions> { order: Direction.asc | Direction.desc; + field: string; + value: string; } -export interface HostFirstLastSeenStrategyResponse extends IEsSearchResponse { +export interface FirstLastSeenStrategyResponse extends IEsSearchResponse { inspect?: Maybe<Inspect>; firstSeen?: Maybe<string>; lastSeen?: Maybe<string>; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts index 5473349bf144b..52ec1aa5b76e8 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/index.ts @@ -8,14 +8,12 @@ export * from './all'; export * from './common'; export * from './details'; -export * from './first_last_seen'; export * from './kpi'; export * from './overview'; export * from './uncommon_processes'; export enum HostsQueries { details = 'hostDetails', - firstOrLastSeen = 'firstOrLastSeen', hosts = 'hosts', overview = 'overviewHost', uncommonProcesses = 'uncommonProcesses', diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts index 1d539daabc011..2648ac98cbbdc 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/index.ts @@ -12,7 +12,6 @@ import { HostDetailsRequestOptions, HostsOverviewStrategyResponse, HostOverviewRequestOptions, - HostFirstLastSeenStrategyResponse, HostsQueries, HostsRequestOptions, HostsStrategyResponse, @@ -23,7 +22,6 @@ import { HostsKpiHostsRequestOptions, HostsKpiUniqueIpsStrategyResponse, HostsKpiUniqueIpsRequestOptions, - HostFirstLastSeenRequestOptions, } from './hosts'; import { NetworkQueries, @@ -93,6 +91,11 @@ import { UserAuthenticationsRequestOptions, UserAuthenticationsStrategyResponse, } from './users/authentications'; +import { + FirstLastSeenQuery, + FirstLastSeenRequestOptions, + FirstLastSeenStrategyResponse, +} from './first_last_seen'; export * from './cti'; export * from './hosts'; @@ -100,6 +103,7 @@ export * from './risk_score'; export * from './matrix_histogram'; export * from './network'; export * from './users'; +export * from './first_last_seen'; export type FactoryQueryTypes = | HostsQueries @@ -109,7 +113,8 @@ export type FactoryQueryTypes = | NetworkKpiQueries | RiskQueries | CtiQueries - | typeof MatrixHistogramQuery; + | typeof MatrixHistogramQuery + | typeof FirstLastSeenQuery; export interface RequestBasicOptions extends IEsSearchRequest { timerange: TimerangeInput; @@ -137,8 +142,8 @@ export type StrategyResponseType<T extends FactoryQueryTypes> = T extends HostsQ ? HostDetailsStrategyResponse : T extends HostsQueries.overview ? HostsOverviewStrategyResponse - : T extends HostsQueries.firstOrLastSeen - ? HostFirstLastSeenStrategyResponse + : T extends typeof FirstLastSeenQuery + ? FirstLastSeenStrategyResponse : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesStrategyResponse : T extends HostsKpiQueries.kpiHosts @@ -199,8 +204,8 @@ export type StrategyRequestType<T extends FactoryQueryTypes> = T extends HostsQu ? HostDetailsRequestOptions : T extends HostsQueries.overview ? HostOverviewRequestOptions - : T extends HostsQueries.firstOrLastSeen - ? HostFirstLastSeenRequestOptions + : T extends typeof FirstLastSeenQuery + ? FirstLastSeenRequestOptions : T extends HostsQueries.uncommonProcesses ? HostsUncommonProcessesRequestOptions : T extends HostsKpiQueries.kpiHosts diff --git a/x-pack/plugins/security_solution/cypress/fixtures/related_integrations.ndjson b/x-pack/plugins/security_solution/cypress/fixtures/related_integrations.ndjson new file mode 100644 index 0000000000000..f121d07f4610f --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/fixtures/related_integrations.ndjson @@ -0,0 +1,2 @@ +{"id":"6cc39c80-da3a-11ec-9fce-65c1a0bee904","updated_at":"2022-05-23T01:48:23.422Z","updated_by":"elastic","created_at":"2022-05-23T01:48:20.940Z","created_by":"elastic","name":"Related integrations rule","tags":["Elastic","Endpoint Security"],"interval":"5m","enabled":false,"description":"Generates a detection alert each time an Elastic Endpoint Security alert is received. Enabling this rule allows you to immediately begin investigating your Endpoint alerts.","risk_score":47,"severity":"medium","license":"Elastic License v2","output_index":".siem-signals-default","meta":{"from":"5m"},"rule_name_override":"message","timestamp_override":"event.ingested","author":["Elastic"],"false_positives":[],"from":"now-50000h","rule_id":"2c66bf23-6ae9-4eb2-859e-446bea181ae9","max_signals":10000,"risk_score_mapping":[{"field":"event.risk_score","operator":"equals","value":""}],"severity_mapping":[{"field":"event.severity","operator":"equals","severity":"low","value":"21"},{"field":"event.severity","operator":"equals","severity":"medium","value":"47"},{"field":"event.severity","operator":"equals","severity":"high","value":"73"},{"field":"event.severity","operator":"equals","severity":"critical","value":"99"}],"threat":[],"to":"now","references":[],"version":7,"exceptions_list":[{"id":"endpoint_list","list_id":"endpoint_list","namespace_type":"agnostic","type":"endpoint"}],"immutable":false,"related_integrations":[{"package":"system","version":"1.17.0"},{"package":"aws","integration":"cloudtrail","version":"1.17.0"},{"package":"aws","integration":"cloudfront","version":"1.17.0"},{"package":"aws","integration":"unknown","version":"1.17.0"}],"type":"query","language":"kuery","index":["auditbeat-*"],"query":"*:*","filters":[],"throttle":"no_actions","actions":[]} +{"exported_count":1,"exported_rules_count":1,"missing_rules":[],"missing_rules_count":0,"exported_exception_list_count":0,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0} diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/related_integrations.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/related_integrations.spec.ts new file mode 100644 index 0000000000000..b5c6b5cd341ec --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/related_integrations.spec.ts @@ -0,0 +1,222 @@ +/* + * 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 { + INTEGRATIONS_POPOVER, + INTEGRATIONS_POPOVER_TITLE, + RULE_NAME, +} from '../../screens/alerts_detection_rules'; +import { INTEGRATIONS, INTEGRATIONS_STATUS } from '../../screens/rule_details'; +import { + enableRule, + goToTheRuleDetailsOf, + openIntegrationsPopover, + waitForRuleToChangeStatus, +} from '../../tasks/alerts_detection_rules'; +import { importRule } from '../../tasks/api_calls/rules'; +import { cleanKibana, cleanPackages } from '../../tasks/common'; +import { login, visit } from '../../tasks/login'; +import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation'; +import { installAwsCloudFrontWithPolicy } from '../../tasks/integrations'; +import { expandFirstAlert } from '../../tasks/alerts'; +import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; +import { filterBy, openTable } from '../../tasks/alerts_details'; +import { FIELD } from '../../screens/alerts_details'; +import { + disableRelatedIntegrations, + enableRelatedIntegrations, +} from '../../tasks/api_calls/kibana_advanced_settings'; + +/* + Note that the rule we are using for testing purposes has the following characteristics, changing that may affect the coverage. + + - Single-integration + - Package: system + - Multi-integration package + - Package: aws + - Integration: cloudtrail + - Integration: cloudfront + - Not existing package: + - Package: unknown + - Not existing integration & existing package: + - Package: aws + - Integration: unknown + */ + +describe('Related integrations', () => { + before(() => { + login(); + cleanKibana(); + importRule('related_integrations.ndjson'); + }); + + context('integrations not installed', () => { + const rule = { + name: 'Related integrations rule', + integrations: ['Aws Cloudfront', 'Aws Cloudtrail', 'Aws Unknown', 'System'], + enabledIntegrations: '0', + }; + + before(() => { + cleanPackages(); + visit(DETECTIONS_RULE_MANAGEMENT_URL); + }); + + it('should display a badge with the installed integrations on the rule management page', () => { + cy.get(INTEGRATIONS_POPOVER).should( + 'have.text', + `${rule.enabledIntegrations}/${rule.integrations.length} integrations` + ); + }); + + it('should display a popover when clicking the badge with the installed integrations on the rule management page', () => { + openIntegrationsPopover(); + + cy.get(INTEGRATIONS_POPOVER_TITLE).should( + 'have.text', + `[${rule.integrations.length}] Related integrations available` + ); + cy.get(INTEGRATIONS).should('have.length', rule.integrations.length); + cy.get(INTEGRATIONS_STATUS).should('have.length', rule.integrations.length); + + rule.integrations.forEach((integration, index) => { + cy.get(INTEGRATIONS).eq(index).should('contain', integration); + cy.get(INTEGRATIONS_STATUS).eq(index).should('have.text', 'Not installed'); + }); + }); + + it('should display the integrations on the definition section', () => { + goToTheRuleDetailsOf(rule.name); + + cy.get(INTEGRATIONS).should('have.length', rule.integrations.length); + cy.get(INTEGRATIONS_STATUS).should('have.length', rule.integrations.length); + + rule.integrations.forEach((integration, index) => { + cy.get(INTEGRATIONS).eq(index).should('contain', integration); + cy.get(INTEGRATIONS_STATUS).eq(index).should('have.text', 'Not installed'); + }); + }); + }); + + context( + 'installed integrations: Amazon CloudFront, AWS CloudTrail, System, enabled integrations: Amazon CloudFront, Aws Cloudfront, System', + () => { + const rule = { + name: 'Related integrations rule', + integrations: [ + { name: 'Amazon CloudFront', installed: true, enabled: true }, + { name: 'AWS CloudTrail', installed: true, enabled: false }, + { name: 'Aws Unknown', installed: false, enabled: false }, + { name: 'System', installed: true, enabled: true }, + ], + enabledIntegrations: '2', + }; + + before(() => { + cleanPackages(); + installAwsCloudFrontWithPolicy(); + visit(DETECTIONS_RULE_MANAGEMENT_URL); + }); + + it('should display a badge with the installed integrations on the rule management page', () => { + cy.get(INTEGRATIONS_POPOVER).should( + 'have.text', + `${rule.enabledIntegrations}/${rule.integrations.length} integrations` + ); + }); + + it('should display a popover when clicking the badge with the installed integrations on the rule management page', () => { + openIntegrationsPopover(); + + cy.get(INTEGRATIONS_POPOVER_TITLE).should( + 'have.text', + `[${rule.integrations.length}] Related integrations available` + ); + cy.get(INTEGRATIONS).should('have.length', rule.integrations.length); + cy.get(INTEGRATIONS_STATUS).should('have.length', rule.integrations.length); + + rule.integrations.forEach((integration, index) => { + let expectedStatus = integration.installed ? 'Installed' : 'Not installed'; + if (integration.enabled) expectedStatus += ': enabled'; + + cy.get(INTEGRATIONS).eq(index).should('contain', integration.name); + cy.get(INTEGRATIONS_STATUS).eq(index).should('have.text', expectedStatus); + }); + }); + + it('should display the integrations on the definition section', () => { + goToTheRuleDetailsOf(rule.name); + + cy.get(INTEGRATIONS).should('have.length', rule.integrations.length); + cy.get(INTEGRATIONS_STATUS).should('have.length', rule.integrations.length); + + rule.integrations.forEach((integration, index) => { + let expectedStatus = integration.installed ? 'Installed' : 'Not installed'; + if (integration.enabled) expectedStatus += ': enabled'; + + cy.get(INTEGRATIONS).eq(index).should('contain', integration.name); + cy.get(INTEGRATIONS_STATUS).eq(index).should('have.text', expectedStatus); + }); + }); + + it('the alerts generated should have a "kibana.alert.rule.parameters.related_integrations" field containing the integrations', () => { + const firstRule = 0; + const relatedIntegrationsField = 'kibana.alert.rule.parameters.related_integrations'; + const expectedRelatedIntegrationsText = + '{"package":"system","version":"1.17.0"}{"package":"aws","integration":"cloudtrail","version":"1.17.0"}{"package":"aws","integration":"cloudfront","version":"1.17.0"}{"package":"aws","integration":"unknown","version":"1.17.0"}'; + + visit(DETECTIONS_RULE_MANAGEMENT_URL); + enableRule(firstRule); + waitForRuleToChangeStatus(); + goToTheRuleDetailsOf(rule.name); + waitForAlertsToPopulate(); + expandFirstAlert(); + openTable(); + filterBy(relatedIntegrationsField); + cy.get(FIELD(relatedIntegrationsField)).should( + 'have.text', + expectedRelatedIntegrationsText + ); + }); + } + ); + + context('related Integrations Advanced Setting is disabled', () => { + const rule = { + name: 'Related integrations rule', + integrations: ['Aws Cloudfront', 'Aws Cloudtrail', 'Aws Unknown', 'System'], + enabledIntegrations: '0', + }; + + before(() => { + cleanPackages(); + disableRelatedIntegrations(); + visit(DETECTIONS_RULE_MANAGEMENT_URL); + }); + + after(() => { + enableRelatedIntegrations(); + }); + + it('should not display a badge with the installed integrations on the rule management page', () => { + cy.get(RULE_NAME).should('have.text', rule.name); + cy.get(INTEGRATIONS).should('not.exist'); + }); + + it('should display the integrations on the definition section', () => { + goToTheRuleDetailsOf(rule.name); + + cy.get(INTEGRATIONS).should('have.length', rule.integrations.length); + cy.get(INTEGRATIONS_STATUS).should('have.length', rule.integrations.length); + + rule.integrations.forEach((integration, index) => { + cy.get(INTEGRATIONS).eq(index).should('contain', integration); + cy.get(INTEGRATIONS_STATUS).eq(index).should('have.text', 'Not installed'); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 4345f71337d62..eeb4b7b5b13ce 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -460,7 +460,7 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema> description, risk_score: riskScore, severity, - output_index: '.siem-signals-default', + output_index: '', author: [], false_positives: [], from: 'now-50000h', 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 f035bbf2e3efd..bba46731d4d53 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -17,6 +17,12 @@ export const ENRICHMENT_QUERY_START_INPUT = '.start-picker'; export const ENRICHMENT_QUERY_END_INPUT = '.end-picker'; +export const FIELD = (value: string) => { + return `[data-test-subj="event-field-${value}"]`; +}; + +export const FILTER_INPUT = '[data-test-subj="eventDetails"] .euiFieldSearch'; + export const INDICATOR_MATCH_ENRICHMENT_SECTION = '[data-test-subj="threat-match-detected"]'; export const INVESTIGATION_TIME_ENRICHMENT_SECTION = diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts index 3556f46ac8534..2e186d95f176c 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts @@ -39,6 +39,10 @@ export const FIRST_RULE = 0; export const FOURTH_RULE = 3; +export const INTEGRATIONS_POPOVER = '[data-test-subj="IntegrationsDisplayPopover"]'; + +export const INTEGRATIONS_POPOVER_TITLE = '[data-test-subj="IntegrationsPopoverTitle"]'; + export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; export const LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN = '[data-test-subj="loadPrebuiltRulesBtn"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/integrations.ts b/x-pack/plugins/security_solution/cypress/screens/integrations.ts new file mode 100644 index 0000000000000..a834fdf0fb050 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/integrations.ts @@ -0,0 +1,14 @@ +/* + * 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 ADD_INTEGRATION_BTN = '[data-test-subj="addIntegrationPolicyButton"]'; + +export const INTEGRATION_ADDED_POP_UP = '[data-test-subj="postInstallAddAgentModal"]'; + +export const QUEUE_URL = '.euiFieldText.euiFormControlLayout--1icons'; + +export const SAVE_AND_CONTINUE_BTN = '[data-test-subj="createPackagePolicySaveButton"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index c7726ac40e83d..d58451f6f1520 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -47,6 +47,10 @@ export const INDICATOR_INDEX_QUERY = 'Indicator index query'; export const INDICATOR_MAPPING = 'Indicator mapping'; +export const INTEGRATIONS = '[data-test-subj="integrationLink"]'; + +export const INTEGRATIONS_STATUS = '[data-test-subj="statusBadge"]'; + export const INVESTIGATION_NOTES_MARKDOWN = 'test markdown'; export const INVESTIGATION_NOTES_TOGGLE = '[data-test-subj="stepAboutDetailsToggle-notes"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts index 87aa83c3c10ec..d8f1aaf36a01e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts @@ -11,8 +11,13 @@ import { JSON_VIEW_TAB, OVERVIEW_TAB, TABLE_TAB, + FILTER_INPUT, } from '../screens/alerts_details'; +export const filterBy = (value: string) => { + cy.get(FILTER_INPUT).type(value); +}; + export const openJsonView = () => { cy.get(JSON_VIEW_TAB).click(); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index b5c50d11deb10..80d5bf54ca6c4 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -46,6 +46,7 @@ import { RULE_IMPORT_OVERWRITE_EXCEPTIONS_CHECKBOX, RULES_TAGS_POPOVER_BTN, RULES_TAGS_POPOVER_WRAPPER, + INTEGRATIONS_POPOVER, SELECTED_RULES_NUMBER_LABEL, } from '../screens/alerts_detection_rules'; import { ALL_ACTIONS } from '../screens/rule_details'; @@ -165,6 +166,10 @@ export const loadPrebuiltDetectionRules = () => { .should('be.disabled'); }; +export const openIntegrationsPopover = () => { + cy.get(INTEGRATIONS_POPOVER).click(); +}; + export const reloadDeletedRules = () => { cy.get(RELOAD_PREBUILT_RULES_BTN).click({ force: true }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.ts new file mode 100644 index 0000000000000..8b370b8e1168c --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/kibana_advanced_settings.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. + */ + +const kibanaSettings = (body: Cypress.RequestBody) => { + cy.request({ + method: 'POST', + url: 'api/kibana/settings', + body, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); +}; + +const relatedIntegrationsBody = (status: boolean): Cypress.RequestBody => { + return { changes: { 'securitySolution:showRelatedIntegrations': status } }; +}; + +export const enableRelatedIntegrations = () => { + kibanaSettings(relatedIntegrationsBody(true)); +}; + +export const disableRelatedIntegrations = () => { + kibanaSettings(relatedIntegrationsBody(false)); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 405c118140395..3f60a8f01bc6e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -131,3 +131,24 @@ export const deleteCustomRule = (ruleId = '1') => { failOnStatusCode: false, }); }; + +export const importRule = (ndjsonPath: string) => { + cy.fixture(ndjsonPath) + .then((file) => Cypress.Blob.binaryStringToBlob(file)) + .then((blob) => { + const formdata = new FormData(); + formdata.append('file', blob, ndjsonPath); + + cy.request({ + url: 'api/detection_engine/rules/_import', + method: 'POST', + headers: { + 'kbn-xsrf': 'cypress-creds', + 'content-type': 'multipart/form-data', + }, + body: formdata, + }) + .its('status') + .should('be.equal', 200); + }); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/common.ts b/x-pack/plugins/security_solution/cypress/tasks/common.ts index a738019d1fcbf..69b88bca934c5 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common.ts @@ -69,6 +69,48 @@ export const cleanKibana = () => { deleteTimelines(); }; +export const cleanPackages = () => { + deletePolicies(); + deletePackages(); +}; + +export const deletePolicies = () => { + cy.request({ + method: 'GET', + url: 'api/fleet/agent_policies', + headers: { 'kbn-xsrf': 'cypress-creds' }, + }).then((response) => { + response.body.items.forEach((item: { id: string }) => { + cy.request({ + method: 'POST', + url: `api/fleet/agent_policies/delete`, + headers: { 'kbn-xsrf': 'cypress-creds' }, + body: { + agentPolicyId: item.id, + }, + }); + }); + }); +}; + +export const deletePackages = () => { + cy.request({ + method: 'GET', + url: 'api/fleet/epm/packages', + headers: { 'kbn-xsrf': 'cypress-creds' }, + }).then((response) => { + response.body.items.forEach((item: { status: string; name: string; version: string }) => { + if (item.status === 'installed') { + cy.request({ + method: 'DELETE', + url: `api/fleet/epm/packages/${item.name}/${item.version}`, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); + } + }); + }); +}; + export const deleteAlertsAndRules = () => { const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/integrations.ts b/x-pack/plugins/security_solution/cypress/tasks/integrations.ts new file mode 100644 index 0000000000000..1da83a591aa12 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/integrations.ts @@ -0,0 +1,23 @@ +/* + * 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 { + ADD_INTEGRATION_BTN, + INTEGRATION_ADDED_POP_UP, + QUEUE_URL, + SAVE_AND_CONTINUE_BTN, +} from '../screens/integrations'; + +import { visit } from './login'; + +export const installAwsCloudFrontWithPolicy = () => { + visit('app/integrations/detail/aws-1.17.0/overview?integration=cloudfront'); + cy.get(ADD_INTEGRATION_BTN).click(); + cy.get(QUEUE_URL).type('http://www.example.com'); + cy.get(SAVE_AND_CONTINUE_BTN).click(); + cy.get(INTEGRATION_ADDED_POP_UP).should('exist'); +}; diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index 244d59a2e76cb..34b717e99145c 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -56,7 +56,8 @@ const StartAppComponent: FC<StartAppComponent> = ({ cases, } = useKibana().services; const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE); - const casesPermissions = useGetUserCasesPermissions(); + const userPermissions = useGetUserCasesPermissions(); + const casesPermissions = { all: userPermissions.crud, read: userPermissions.read }; const CasesContext = cases.ui.getCasesContext(); return ( <EuiErrorBoundary> @@ -69,10 +70,7 @@ const StartAppComponent: FC<StartAppComponent> = ({ <UserPrivilegesProvider kibanaCapabilities={capabilities}> <ManageUserInfo> <ReactQueryClientProvider> - <CasesContext - owner={[APP_ID]} - userCanCrud={casesPermissions?.crud ?? false} - > + <CasesContext owner={[APP_ID]} permissions={casesPermissions}> <PageRouter history={history} onAppLeave={onAppLeave} diff --git a/x-pack/plugins/security_solution/public/cases/links.ts b/x-pack/plugins/security_solution/public/cases/links.ts index bce1b84a23d2f..57e01813c77ed 100644 --- a/x-pack/plugins/security_solution/public/cases/links.ts +++ b/x-pack/plugins/security_solution/public/cases/links.ts @@ -15,7 +15,7 @@ export const getCasesLinkItems = (): LinkItem => { extend: { [SecurityPageName.case]: { globalNavEnabled: true, - globalNavOrder: 4, + globalNavOrder: 5, capabilities: [`${CASES_FEATURE_ID}.read_cases`], }, [SecurityPageName.caseConfigure]: { diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index a3bfdab9f9114..9eb4c57da74c2 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -46,6 +46,7 @@ const CaseContainerComponent: React.FC = () => { const { cases } = useKibana().services; const { getAppUrl, navigateTo } = useNavigation(); const userPermissions = useGetUserCasesPermissions(); + const casesPermissions = { all: userPermissions.crud, read: userPermissions.read }; const dispatch = useDispatch(); const { formatUrl: detectionsFormatUrl, search: detectionsUrlSearch } = useFormatUrl( SecurityPageName.rules @@ -147,7 +148,7 @@ const CaseContainerComponent: React.FC = () => { }, }, useFetchAlertData, - userCanCrud: userPermissions?.crud ?? false, + permissions: casesPermissions, })} </CaseDetailsRefreshContext.Provider> <SpyRoute pageName={SecurityPageName.case} /> diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/index.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/index.ts new file mode 100644 index 0000000000000..798658b15cd42 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SecuritySubPlugin } from '../app/types'; +import { routes } from './routes'; + +export class CloudSecurityPosture { + public setup() {} + + public start(): SecuritySubPlugin { + return { routes }; + } +} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts new file mode 100644 index 0000000000000..f862f86fc7247 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/links.ts @@ -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 { i18n } from '@kbn/i18n'; +import { IconExceptionLists } from '../management/icons/exception_lists'; +import { + CLOUD_SECURITY_POSTURE_DASHBOARD_PATH, + CLOUD_SECURITY_POSTURE_FINDINGS_PATH, + CLOUD_SECURITY_POSTURE_BENCHMARKS_PATH, + SecurityPageName, +} from '../../common/constants'; +import type { LinkItem, LinkCategories } from '../common/links/types'; +import cloudSecurityPostureDashboardImage from '../common/images/cloud_security_posture_dashboard_page.png'; + +const commonLinkProperties: Partial<LinkItem> = { + skipUrlState: true, + hideTimeline: true, + experimentalKey: 'cloudSecurityPostureNavigation', +}; + +export const rootLinks: LinkItem = { + id: SecurityPageName.cloudSecurityPostureFindings, + title: i18n.translate('xpack.securitySolution.appLinks.findings', { + defaultMessage: 'Findings ', + }), + path: CLOUD_SECURITY_POSTURE_FINDINGS_PATH, + globalNavEnabled: true, + globalNavOrder: 3, + ...commonLinkProperties, +}; + +export const dashboardLinks: LinkItem = { + id: SecurityPageName.cloudSecurityPostureDashboard, + title: i18n.translate('xpack.securitySolution.appLinks.cloudSecurityPostureDashboard', { + defaultMessage: 'Cloud Posture', + }), + path: CLOUD_SECURITY_POSTURE_DASHBOARD_PATH, + description: i18n.translate( + 'xpack.securitySolution.appLinks.cloudSecurityPostureDashboardDescription', + { + defaultMessage: 'An overview of findings across all CSP integrations.', + } + ), + landingImage: cloudSecurityPostureDashboardImage, + ...commonLinkProperties, +}; + +export const manageLinks: LinkItem = { + id: SecurityPageName.cloudSecurityPostureBenchmarks, + title: i18n.translate('xpack.securitySolution.appLinks.cloudSecurityPostureBenchmarks', { + defaultMessage: 'CSP Benchmarks', + }), + path: CLOUD_SECURITY_POSTURE_BENCHMARKS_PATH, + description: i18n.translate( + 'xpack.securitySolution.appLinks.cloudSecurityPostureBenchmarksDescription', + { + defaultMessage: 'View, enable, and or disable benchmark rules.', + } + ), + // TODO: Temporary until we have a CSP icon + landingIcon: IconExceptionLists, + ...commonLinkProperties, +}; + +export const manageCategories: LinkCategories = [ + { + label: i18n.translate('xpack.securitySolution.appLinks.category.cloudSecurityPosture', { + defaultMessage: 'CLOUD SECURITY POSTURE', + }), + linkIds: [SecurityPageName.cloudSecurityPostureBenchmarks], + }, +]; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/routes.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/routes.tsx new file mode 100644 index 0000000000000..14cc141e30430 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/routes.tsx @@ -0,0 +1,60 @@ +/* + * 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 { EuiTitle } from '@elastic/eui'; +import React from 'react'; +import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; +import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper'; +import { SpyRoute } from '../common/utils/route/spy_routes'; +import { + CLOUD_SECURITY_POSTURE_BENCHMARKS_PATH, + CLOUD_SECURITY_POSTURE_DASHBOARD_PATH, + CLOUD_SECURITY_POSTURE_FINDINGS_PATH, + CLOUD_SECURITY_POSTURE_PATH, + SecurityPageName, +} from '../../common/constants'; +import type { SecuritySubPluginRoutes } from '../app/types'; + +const CloudSecurityPosture = ({ pageName }: { pageName: SecurityPageName }) => { + return ( + <TrackApplicationView viewId={pageName}> + <SecuritySolutionPageWrapper noPadding noTimeline> + <SpyRoute pageName={pageName} /> + <EuiTitle> + <h1>{'Coming soon'}</h1> + </EuiTitle> + </SecuritySolutionPageWrapper> + </TrackApplicationView> + ); +}; + +// TODO: We'll probably use a single route here, and we'll manage all CSP pages in an internal router in the CSP plugin. +// For now we have multiple routes as we need `SpyRoute` to use a specific `pageName` for highlighting the correct +// navigation bar entry +export const routes: SecuritySubPluginRoutes = [ + { + path: CLOUD_SECURITY_POSTURE_PATH, + render: () => <CloudSecurityPosture pageName={SecurityPageName.cloudSecurityPosture} />, + exact: true, + }, + { + path: CLOUD_SECURITY_POSTURE_FINDINGS_PATH, + render: () => <CloudSecurityPosture pageName={SecurityPageName.cloudSecurityPostureFindings} />, + }, + { + path: CLOUD_SECURITY_POSTURE_DASHBOARD_PATH, + render: () => ( + <CloudSecurityPosture pageName={SecurityPageName.cloudSecurityPostureDashboard} /> + ), + }, + { + path: CLOUD_SECURITY_POSTURE_BENCHMARKS_PATH, + render: () => ( + <CloudSecurityPosture pageName={SecurityPageName.cloudSecurityPostureBenchmarks} /> + ), + }, +]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 12b5bc755d35f..bd4170a9cbbe7 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -20,8 +20,16 @@ import { mockAlertDetailsData } from './__mocks__'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { TimelineTabs } from '../../../../common/types/timeline'; import { useInvestigationTimeEnrichment } from '../../containers/cti/event_enrichment'; +import { useGetUserCasesPermissions } from '../../lib/kibana'; jest.mock('../../lib/kibana'); +const originalKibanaLib = jest.requireActual('../../lib/kibana'); + +// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object +// The returned permissions object will indicate that the user does not have permissions by default +const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock; +mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions); + jest.mock('../../containers/cti/event_enrichment'); jest.mock('../../../detections/containers/detection_engine/rules/use_rule_with_fallback', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.tsx index 86cc751cb5754..d90688d80a86e 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/related_cases.tsx @@ -29,7 +29,7 @@ export const RelatedCases: React.FC<Props> = React.memo(({ eventId, isReadOnly } const [relatedCases, setRelatedCases] = useState<RelatedCaseList>([]); const [areCasesLoading, setAreCasesLoading] = useState(true); const [hasError, setHasError] = useState<boolean>(false); - const hasCasesReadPermissions = casePermissions?.read ?? false; + const hasCasesReadPermissions = casePermissions.read; const getRelatedCases = useCallback(async () => { let relatedCaseList: RelatedCaseList = []; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx index 124dc4eefb997..a3d4bf6f42557 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx @@ -24,9 +24,17 @@ import { getDefaultControlColumn } from '../../../timelines/components/timeline/ import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions'; import { UseFieldBrowserOptionsProps } from '../../../timelines/components/fields_browser'; +import { useGetUserCasesPermissions } from '../../lib/kibana'; jest.mock('../../lib/kibana'); +const originalKibanaLib = jest.requireActual('../../lib/kibana'); + +// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object +// The returned permissions object will indicate that the user does not have permissions by default +const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock; +mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions); + jest.mock('../../../timelines/containers', () => ({ useTimelineEvents: jest.fn(), })); diff --git a/x-pack/plugins/security_solution/public/common/components/first_last_seen/first_last_seen.test.tsx b/x-pack/plugins/security_solution/public/common/components/first_last_seen/first_last_seen.test.tsx new file mode 100644 index 0000000000000..32c356ae96fe8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/first_last_seen/first_last_seen.test.tsx @@ -0,0 +1,116 @@ +/* + * 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, waitFor } from '@testing-library/react'; + +import { useFirstLastSeen } from '../../containers/use_first_last_seen'; +import { TestProviders } from '../../mock'; +import { FirstLastSeen, FirstLastSeenProps, FirstLastSeenType } from './first_last_seen'; + +const MOCKED_RESPONSE = { + firstSeen: '2019-04-08T16:09:40.692Z', + lastSeen: '2022-04-08T18:35:45.064Z', +}; + +jest.mock('../../containers/use_first_last_seen'); +const useFirstLastSeenMock = useFirstLastSeen as jest.Mock; +useFirstLastSeenMock.mockReturnValue([false, MOCKED_RESPONSE]); + +describe('FirstLastSeen Component', () => { + const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; + const lastSeen = 'Apr 8, 2022 @ 18:35:45.064'; + + const renderComponent = (overrides?: Partial<FirstLastSeenProps>) => { + return render( + <TestProviders> + <FirstLastSeen + field="host.name" + value="some-host" + indexPatterns={[]} + type={FirstLastSeenType.FIRST_SEEN} + {...overrides} + /> + </TestProviders> + ); + }; + + test('Loading', async () => { + useFirstLastSeenMock.mockReturnValue([true, MOCKED_RESPONSE]); + + const { getByTestId } = renderComponent(); + + expect(getByTestId('loading-spinner')).toBeInTheDocument(); + }); + + test('First Seen', async () => { + useFirstLastSeenMock.mockReturnValue([false, MOCKED_RESPONSE]); + + const { getByText } = renderComponent(); + + await waitFor(() => { + expect(getByText(firstSeen)).toBeInTheDocument(); + }); + }); + + test('Last Seen', async () => { + useFirstLastSeenMock.mockReturnValue([false, MOCKED_RESPONSE]); + + const { getByText } = renderComponent({ type: FirstLastSeenType.LAST_SEEN }); + + await waitFor(() => { + expect(getByText(lastSeen)).toBeInTheDocument(); + }); + }); + + test('First Seen is empty but not Last Seen', async () => { + useFirstLastSeenMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + firstSeen: null, + }, + ]); + + const { getByText } = renderComponent({ type: FirstLastSeenType.LAST_SEEN }); + + await waitFor(() => { + expect(getByText(lastSeen)).toBeInTheDocument(); + }); + }); + + test('Last Seen is empty but not First Seen', async () => { + useFirstLastSeenMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + lastSeen: null, + }, + ]); + + const { getByText } = renderComponent({ type: FirstLastSeenType.FIRST_SEEN }); + + await waitFor(() => { + expect(getByText(firstSeen)).toBeInTheDocument(); + }); + }); + + test('With a bad date time string', async () => { + useFirstLastSeenMock.mockReturnValue([ + false, + { + ...MOCKED_RESPONSE, + firstSeen: 'something-invalid', + }, + ]); + const { getByText } = renderComponent(); + await waitFor(() => { + expect(getByText('something-invalid')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/first_last_seen/first_last_seen.tsx b/x-pack/plugins/security_solution/public/common/components/first_last_seen/first_last_seen.tsx new file mode 100644 index 0000000000000..6bb66716b77a2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/first_last_seen/first_last_seen.tsx @@ -0,0 +1,73 @@ +/* + * 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, { useMemo } from 'react'; + +import { EuiIcon, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui'; + +import { useFirstLastSeen } from '../../containers/use_first_last_seen'; +import { getEmptyTagValue } from '../empty_value'; +import { FormattedRelativePreferenceDate } from '../formatted_date'; +import { Direction } from '../../../../common/search_strategy'; + +export enum FirstLastSeenType { + FIRST_SEEN = 'first-seen', + LAST_SEEN = 'last-seen', +} + +export interface FirstLastSeenProps { + indexPatterns: string[]; + field: string; + type: FirstLastSeenType; + value: string; +} + +export const FirstLastSeen = React.memo<FirstLastSeenProps>( + ({ indexPatterns, field, type, value }) => { + const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeen({ + field, + value, + order: type === FirstLastSeenType.FIRST_SEEN ? Direction.asc : Direction.desc, + defaultIndex: indexPatterns, + }); + const valueSeen = useMemo( + () => (type === FirstLastSeenType.FIRST_SEEN ? firstSeen : lastSeen), + [firstSeen, lastSeen, type] + ); + + if (errorMessage != null) { + return ( + <EuiToolTip + position="top" + content={errorMessage} + data-test-subj="firstLastSeenErrorToolTip" + aria-label={`firstLastSeenError-${type}`} + id={`firstLastSeenError-${field}-${type}`} + > + <EuiIcon aria-describedby={`firstLastSeenError-${field}-${type}`} type="alert" /> + </EuiToolTip> + ); + } + + return ( + <> + {loading && <EuiLoadingSpinner data-test-subj="loading-spinner" size="m" />} + {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' + ? valueSeen + : !loading && + valueSeen !== null && ( + <EuiText data-test-subj="first-last-seen-value" size="s"> + <FormattedRelativePreferenceDate value={`${valueSeen}`} /> + </EuiText> + )} + {!loading && valueSeen === null && getEmptyTagValue()} + </> + ); + } +); + +FirstLastSeen.displayName = 'FirstLastSeen'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_settings/index.ts b/x-pack/plugins/security_solution/public/common/components/first_last_seen/index.ts similarity index 82% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_settings/index.ts rename to x-pack/plugins/security_solution/public/common/components/first_last_seen/index.ts index 699c96532b018..3d98b44bb0b71 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_settings/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/first_last_seen/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { CrawlerSettings } from './crawler_settings'; +export * from './first_last_seen'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx index 3162df787af1c..ab05936ebd36a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.test.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { LinkCategories } from '../../../links'; import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { SecurityPageName } from '../../../../app/types'; @@ -37,6 +38,17 @@ const mockItems: DefaultSideNavItem[] = [ }, ]; +const mockCategories: LinkCategories = [ + { + label: 'HOSTS CATEGORY', + linkIds: [SecurityPageName.hosts], + }, + { + label: 'Empty category', + linkIds: [], + }, +]; + const PANEL_TITLE = 'test title'; const mockOnClose = jest.fn(); const mockOnOutsideClick = jest.fn(); @@ -75,6 +87,18 @@ describe('SolutionGroupedNav', () => { }); }); + it('should only render categories with items', () => { + const result = renderNavPanel({ categories: mockCategories }); + + mockCategories.forEach((mockCategory) => { + if (mockCategory.linkIds.length) { + expect(result.getByText(mockCategory.label)).toBeInTheDocument(); + } else { + expect(result.queryByText(mockCategory.label)).not.toBeInTheDocument(); + } + }); + }); + describe('links', () => { it('should contain correct href in links', () => { const result = renderNavPanel(); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx index 4c0ccc6116703..a2773f2223247 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/solution_grouped_nav/solution_grouped_nav_panel.tsx @@ -138,6 +138,10 @@ const SolutionNavPanelCategories: React.FC<SolutionNavPanelCategoriesProps> = ({ return acc; }, []); + if (!links.length) { + return null; + } + return ( <Fragment key={label}> <EuiTitle size="xxxs"> diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap index 293fddddf1ba2..0e178a70a6c51 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/__snapshots__/index.test.tsx.snap @@ -131,6 +131,16 @@ Object { "name": "Timelines", "onClick": [Function], }, + Object { + "data-href": "securitySolutionUI/cases?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-test-subj": "navigation-cases", + "disabled": false, + "href": "securitySolutionUI/cases?query=(language:kuery,query:'host.name:%22security-solution-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "id": "cases", + "isSelected": false, + "name": "Cases", + "onClick": [Function], + }, ], "name": "Investigate", }, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index db7d06f899b5c..4c0861e7a3328 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -23,6 +23,13 @@ import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pag jest.mock('../../../lib/kibana/kibana_react'); jest.mock('../../../lib/kibana'); +const originalKibanaLib = jest.requireActual('../../../lib/kibana'); + +// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object +// The returned permissions object will indicate that the user does not have permissions by default +const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock; +mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions); + jest.mock('../../../hooks/use_selector'); jest.mock('../../../hooks/use_experimental_features'); jest.mock('../../../utils/route/use_route_spy'); @@ -132,7 +139,7 @@ describe('useSecuritySolutionNavigation', () => { }); it('should omit host isolation exceptions if hook reports false', () => { - (useCanSeeHostIsolationExceptionsMenu as jest.Mock).mockReturnValueOnce(false); + (useCanSeeHostIsolationExceptionsMenu as jest.Mock).mockReturnValue(false); const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( () => useSecuritySolutionNavigation(), { wrapper: TestProviders } diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index f9a766ed3d875..090061398ae89 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -68,7 +68,7 @@ export const usePrimaryNavigationItems = ({ }; function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) { - const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; + const hasCasesReadPermissions = useGetUserCasesPermissions().read; const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu(); const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled'); const uiCapabilities = useKibana().services.application.capabilities; diff --git a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx index 5280f298ba99e..cad74583d7596 100644 --- a/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/sessions_viewer/index.test.tsx @@ -12,9 +12,17 @@ import { TEST_ID, SessionsView, defaultSessionsFilter } from '.'; import { EntityType, TimelineId } from '@kbn/timelines-plugin/common'; import { SessionsComponentsProps } from './types'; import { TimelineModel } from '../../../timelines/store/timeline/model'; +import { useGetUserCasesPermissions } from '../../lib/kibana'; jest.mock('../../lib/kibana'); +const originalKibanaLib = jest.requireActual('../../lib/kibana'); + +// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object +// The returned permissions object will indicate that the user does not have permissions by default +const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock; +mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions); + jest.mock('../url_state/normalize_time_range'); const startDate = '2022-03-22T22:10:56.794Z'; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx index 420c2e3dd6c78..ebb46a6484d0f 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/index.tsx @@ -14,7 +14,6 @@ import { ModalInspectQuery } from '../inspect/modal'; import { useInspect } from '../inspect/use_inspect'; import { useLensAttributes } from './use_lens_attributes'; import { useAddToExistingCase } from './use_add_to_existing_case'; -import { useGetUserCasesPermissions } from '../../lib/kibana'; import { useAddToNewCase } from './use_add_to_new_case'; import { VisualizationActionsProps } from './types'; import { @@ -54,8 +53,6 @@ const VisualizationActionsComponent: React.FC<VisualizationActionsProps> = ({ stackByField, }) => { const { lens } = useKibana().services; - const userPermissions = useGetUserCasesPermissions(); - const userCanCrud = userPermissions?.crud ?? false; const { canUseEditor, navigateToPrefilledEditor } = lens; const [isPopoverOpen, setPopover] = useState(false); @@ -82,14 +79,12 @@ const VisualizationActionsComponent: React.FC<VisualizationActionsProps> = ({ onAddToCaseClicked: closePopover, lensAttributes: attributes, timeRange: timerange, - userCanCrud, }); const { onAddToNewCaseClicked, disabled: isAddToNewCaseDisabled } = useAddToNewCase({ onClick: closePopover, timeRange: timerange, lensAttributes: attributes, - userCanCrud, }); const onOpenInLens = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx index 41b4fddf5bd0b..83f1290ab54e3 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.test.tsx @@ -5,25 +5,45 @@ * 2.0. */ import { renderHook } from '@testing-library/react-hooks'; -import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; -import { useKibana } from '../../lib/kibana'; +import { useKibana as mockUseKibana } from '../../lib/kibana/__mocks__'; import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric'; import { useAddToExistingCase } from './use_add_to_existing_case'; +import { useGetUserCasesPermissions } from '../../lib/kibana'; -jest.mock('../../lib/kibana/kibana_react'); +const mockedUseKibana = mockUseKibana(); +const mockGetUseCasesAddToExistingCaseModal = jest.fn(); + +jest.mock('../../lib/kibana', () => { + const original = jest.requireActual('../../lib/kibana'); + + return { + ...original, + useGetUserCasesPermissions: jest.fn(), + useKibana: () => ({ + ...mockedUseKibana, + services: { + ...mockedUseKibana.services, + cases: { + hooks: { + getUseCasesAddToExistingCaseModal: mockGetUseCasesAddToExistingCaseModal, + }, + }, + }, + }), + }; +}); describe('useAddToExistingCase', () => { - const mockCases = mockCasesContract(); const mockOnAddToCaseClicked = jest.fn(); const timeRange = { from: '2022-03-06T16:00:00.000Z', to: '2022-03-07T15:59:59.999Z', }; + beforeEach(() => { - (useKibana as jest.Mock).mockReturnValue({ - services: { - cases: mockCases, - }, + (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ + crud: true, + read: true, }); }); @@ -32,47 +52,48 @@ describe('useAddToExistingCase', () => { useAddToExistingCase({ lensAttributes: kpiHostMetricLensAttributes, timeRange, - userCanCrud: true, onAddToCaseClicked: mockOnAddToCaseClicked, }) ); - expect(mockCases.hooks.getUseCasesAddToExistingCaseModal).toHaveBeenCalledWith({ + expect(mockGetUseCasesAddToExistingCaseModal).toHaveBeenCalledWith({ onClose: mockOnAddToCaseClicked, toastContent: 'Successfully added visualization to the case', }); expect(result.current.disabled).toEqual(false); }); - it("button disalbled if user Can't Crud", () => { + it("button disabled if user Can't Crud", () => { + (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ + crud: false, + read: true, + }); + const { result } = renderHook(() => useAddToExistingCase({ lensAttributes: kpiHostMetricLensAttributes, timeRange, - userCanCrud: false, onAddToCaseClicked: mockOnAddToCaseClicked, }) ); expect(result.current.disabled).toEqual(true); }); - it('button disalbled if no lensAttributes', () => { + it('button disabled if no lensAttributes', () => { const { result } = renderHook(() => useAddToExistingCase({ lensAttributes: null, timeRange, - userCanCrud: true, onAddToCaseClicked: mockOnAddToCaseClicked, }) ); expect(result.current.disabled).toEqual(true); }); - it('button disalbled if no timeRange', () => { + it('button disabled if no timeRange', () => { const { result } = renderHook(() => useAddToExistingCase({ lensAttributes: kpiHostMetricLensAttributes, timeRange: null, - userCanCrud: true, onAddToCaseClicked: mockOnAddToCaseClicked, }) ); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx index a436a2d7e0424..ed8da682bbfd7 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_existing_case.tsx @@ -8,7 +8,7 @@ import { useCallback, useMemo } from 'react'; import { CommentType } from '@kbn/cases-plugin/common'; import { APP_ID } from '../../../../common/constants'; -import { useKibana } from '../../lib/kibana/kibana_react'; +import { useKibana, useGetUserCasesPermissions } from '../../lib/kibana'; import { ADD_TO_CASE_SUCCESS } from './translations'; import { LensAttributes } from './types'; @@ -19,13 +19,12 @@ export const useAddToExistingCase = ({ onAddToCaseClicked, lensAttributes, timeRange, - userCanCrud, }: { onAddToCaseClicked?: () => void; lensAttributes: LensAttributes | null; timeRange: { from: string; to: string } | null; - userCanCrud: boolean; }) => { + const userPermissions = useGetUserCasesPermissions(); const { cases } = useKibana().services; const attachments = useMemo(() => { return [ @@ -54,6 +53,6 @@ export const useAddToExistingCase = ({ return { onAddToExistingCaseClicked, - disabled: lensAttributes == null || timeRange == null || !userCanCrud, + disabled: lensAttributes == null || timeRange == null || !userPermissions.crud, }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx index 583d1eda66a1c..49fbde71386a5 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.test.tsx @@ -5,24 +5,45 @@ * 2.0. */ import { renderHook } from '@testing-library/react-hooks'; -import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; -import { useKibana } from '../../lib/kibana'; +import { useKibana as mockUseKibana } from '../../lib/kibana/__mocks__'; import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric'; import { useAddToNewCase } from './use_add_to_new_case'; +import { useGetUserCasesPermissions } from '../../lib/kibana'; jest.mock('../../lib/kibana/kibana_react'); +const mockedUseKibana = mockUseKibana(); +const mockGetUseCasesAddToNewCaseFlyout = jest.fn(); + +jest.mock('../../lib/kibana', () => { + const original = jest.requireActual('../../lib/kibana'); + + return { + ...original, + useGetUserCasesPermissions: jest.fn(), + useKibana: () => ({ + ...mockedUseKibana, + services: { + ...mockedUseKibana.services, + cases: { + hooks: { + getUseCasesAddToNewCaseFlyout: mockGetUseCasesAddToNewCaseFlyout, + }, + }, + }, + }), + }; +}); + describe('useAddToNewCase', () => { - const mockCases = mockCasesContract(); const timeRange = { from: '2022-03-06T16:00:00.000Z', to: '2022-03-07T15:59:59.999Z', }; beforeEach(() => { - (useKibana as jest.Mock).mockReturnValue({ - services: { - cases: mockCases, - }, + (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ + crud: true, + read: true, }); }); @@ -31,43 +52,44 @@ describe('useAddToNewCase', () => { useAddToNewCase({ lensAttributes: kpiHostMetricLensAttributes, timeRange, - userCanCrud: true, }) ); - expect(mockCases.hooks.getUseCasesAddToNewCaseFlyout).toHaveBeenCalledWith({ + expect(mockGetUseCasesAddToNewCaseFlyout).toHaveBeenCalledWith({ toastContent: 'Successfully added visualization to the case', }); expect(result.current.disabled).toEqual(false); }); - it("button disalbled if user Can't Crud", () => { + it("button disabled if user Can't Crud", () => { + (useGetUserCasesPermissions as jest.Mock).mockReturnValue({ + crud: false, + read: true, + }); + const { result } = renderHook(() => useAddToNewCase({ lensAttributes: kpiHostMetricLensAttributes, timeRange, - userCanCrud: false, }) ); expect(result.current.disabled).toEqual(true); }); - it('button disalbled if no lensAttributes', () => { + it('button disabled if no lensAttributes', () => { const { result } = renderHook(() => useAddToNewCase({ lensAttributes: null, timeRange, - userCanCrud: true, }) ); expect(result.current.disabled).toEqual(true); }); - it('button disalbled if no timeRange', () => { + it('button disabled if no timeRange', () => { const { result } = renderHook(() => useAddToNewCase({ lensAttributes: kpiHostMetricLensAttributes, timeRange: null, - userCanCrud: true, }) ); expect(result.current.disabled).toEqual(true); diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx index 7fa206cfff3a8..42d25036afbeb 100644 --- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx +++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_add_to_new_case.tsx @@ -9,7 +9,7 @@ import { useCallback, useMemo } from 'react'; import { CommentType } from '@kbn/cases-plugin/common'; import { APP_ID } from '../../../../common/constants'; -import { useKibana } from '../../lib/kibana/kibana_react'; +import { useKibana, useGetUserCasesPermissions } from '../../lib/kibana'; import { ADD_TO_CASE_SUCCESS } from './translations'; import { LensAttributes } from './types'; @@ -18,17 +18,12 @@ export interface UseAddToNewCaseProps { onClick?: () => void; timeRange: { from: string; to: string } | null; lensAttributes: LensAttributes | null; - userCanCrud: boolean; } const owner = APP_ID; -export const useAddToNewCase = ({ - onClick, - timeRange, - lensAttributes, - userCanCrud, -}: UseAddToNewCaseProps) => { +export const useAddToNewCase = ({ onClick, timeRange, lensAttributes }: UseAddToNewCaseProps) => { + const userPermissions = useGetUserCasesPermissions(); const { cases } = useKibana().services; const attachments = useMemo(() => { return [ @@ -57,6 +52,6 @@ export const useAddToNewCase = ({ return { onAddToNewCaseClicked, - disabled: lensAttributes == null || timeRange == null || !userCanCrud, + disabled: lensAttributes == null || timeRange == null || !userPermissions.crud, }; }; diff --git a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/index.ts b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/index.ts new file mode 100644 index 0000000000000..f96eeac3f5e47 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/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 * from './use_first_last_seen'; diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/translations.ts b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/translations.ts rename to x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/translations.ts diff --git a/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts new file mode 100644 index 0000000000000..bbbe7cb3902e7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.test.ts @@ -0,0 +1,205 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; + +import { Direction } from '../../../../common/search_strategy'; +import { FirstLastSeenProps } from '../../components/first_last_seen/first_last_seen'; +import { useKibana } from '../../lib/kibana'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import * as i18n from './translations'; +import { UseFirstLastSeen, useFirstLastSeen } from './use_first_last_seen'; + +jest.mock('../../lib/kibana'); +jest.mock('../../hooks/use_app_toasts'); + +const firstSeen = '2022-06-03T19:48:36.165Z'; +const lastSeen = '2022-06-13T19:48:36.165Z'; + +const mockSearchStrategy = jest.fn(); + +const mockAddError = jest.fn(); +const mockAddWarning = jest.fn(); + +(useAppToasts as jest.Mock).mockReturnValue({ + addError: mockAddError, + addWarning: mockAddWarning, +}); + +const mockKibana = (useKibana as jest.Mock).mockReturnValue({ + services: { + data: { + search: { + search: mockSearchStrategy.mockReturnValue({ + unsubscribe: jest.fn(), + subscribe: jest.fn(({ next, error }) => { + next({ firstSeen }); + return { + unsubscribe: jest.fn(), + }; + }), + }), + }, + query: jest.fn(), + }, + }, +}); + +const renderUseFirstLastSeen = (overrides?: Partial<UseFirstLastSeen>) => + renderHook<FirstLastSeenProps, ReturnType<typeof useFirstLastSeen>>(() => + useFirstLastSeen({ + order: Direction.asc, + field: 'host.name', + value: 'some-host', + defaultIndex: [], + ...overrides, + }) + ); + +describe('useFistLastSeen', () => { + it('should return default values', () => { + mockSearchStrategy.mockReturnValueOnce({ + subscribe: jest.fn(), + }); + const { result } = renderUseFirstLastSeen(); + + expect(result.current).toEqual([ + true, + { + errorMessage: null, + firstSeen: null, + id: 'firstLastSeenQuery', + lastSeen: null, + order: null, + }, + ]); + }); + + it('should return parsed items for first seen', () => { + const { result } = renderUseFirstLastSeen(); + + expect(mockSearchStrategy).toHaveBeenCalledWith( + { + defaultIndex: [], + factoryQueryType: 'firstlastseen', + field: 'host.name', + order: 'asc', + value: 'some-host', + }, + { + abortSignal: new AbortController().signal, + strategy: 'securitySolutionSearchStrategy', + } + ); + + expect(result.current).toEqual([ + false, + { + errorMessage: null, + firstSeen: '2022-06-03T19:48:36.165Z', + id: 'firstLastSeenQuery', + order: null, + }, + ]); + }); + + it('should return parsed items for last seen', () => { + mockKibana.mockReturnValueOnce({ + services: { + data: { + search: { + search: mockSearchStrategy.mockReturnValue({ + unsubscribe: jest.fn(), + subscribe: jest.fn(({ next, error }) => { + next({ lastSeen }); + return { + unsubscribe: jest.fn(), + }; + }), + }), + }, + query: jest.fn(), + }, + }, + }); + const { result } = renderUseFirstLastSeen({ order: Direction.desc }); + + expect(mockSearchStrategy).toHaveBeenCalledWith( + { + defaultIndex: [], + factoryQueryType: 'firstlastseen', + field: 'host.name', + order: 'desc', + value: 'some-host', + }, + { + abortSignal: new AbortController().signal, + strategy: 'securitySolutionSearchStrategy', + } + ); + + expect(result.current).toEqual([ + false, + { + errorMessage: null, + lastSeen: '2022-06-13T19:48:36.165Z', + id: 'firstLastSeenQuery', + order: null, + }, + ]); + }); + it('should handle a partial, no longer running response', () => { + mockKibana.mockReturnValueOnce({ + services: { + data: { + search: { + search: mockSearchStrategy.mockReturnValue({ + unsubscribe: jest.fn(), + subscribe: jest.fn(({ next, error }) => { + next({ isRunning: false, isPartial: true }); + return { + unsubscribe: jest.fn(), + }; + }), + }), + }, + query: jest.fn(), + }, + }, + }); + + renderUseFirstLastSeen({ order: Direction.desc }); + expect(mockAddWarning).toHaveBeenCalledWith(i18n.ERROR_FIRST_LAST_SEEN_HOST); + }); + + it('should handle an error with search strategy', () => { + const msg = 'What in tarnation!?'; + mockKibana.mockReturnValueOnce({ + services: { + data: { + search: { + search: mockSearchStrategy.mockReturnValue({ + unsubscribe: jest.fn(), + subscribe: jest.fn(({ next, error }) => { + error(msg); + return { + unsubscribe: jest.fn(), + }; + }), + }), + }, + query: jest.fn(), + }, + }, + }); + + renderUseFirstLastSeen({ order: Direction.desc }); + expect(mockAddError).toHaveBeenCalledWith(msg, { + title: i18n.FAIL_FIRST_LAST_SEEN_HOST, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx similarity index 56% rename from x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx rename to x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx index e6174801e69cb..674e681a4dd3a 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_first_last_seen/use_first_last_seen.tsx @@ -10,70 +10,69 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; import { isCompleteResponse, isErrorResponse } from '@kbn/data-plugin/common'; -import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { useKibana } from '../../../../common/lib/kibana'; -import { - HostsQueries, - HostFirstLastSeenStrategyResponse, - HostFirstLastSeenRequestOptions, -} from '../../../../../common/search_strategy/security_solution'; +import { + Direction, + FirstLastSeenQuery, + FirstLastSeenRequestOptions, + FirstLastSeenStrategyResponse, +} from '../../../../common/search_strategy'; +import { useAppToasts } from '../../hooks/use_app_toasts'; +import { useKibana } from '../../lib/kibana'; import * as i18n from './translations'; -import { Direction, DocValueFields } from '../../../../../common/search_strategy'; -const ID = 'firstLastSeenHostQuery'; +const ID = 'firstLastSeenQuery'; -export interface FirstLastSeenHostArgs { +export interface FirstLastSeenArgs { id: string; errorMessage: string | null; firstSeen?: string | null; lastSeen?: string | null; order: Direction.asc | Direction.desc | null; } -interface UseHostFirstLastSeen { - docValueFields: DocValueFields[]; - hostName: string; - indexNames: string[]; +export interface UseFirstLastSeen { + field: string; + value: string; order: Direction.asc | Direction.desc; + defaultIndex: string[]; } -export const useFirstLastSeenHost = ({ - docValueFields, - hostName, - indexNames, +export const useFirstLastSeen = ({ + field, + value, order, -}: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { - const { data } = useKibana().services; + defaultIndex, +}: UseFirstLastSeen): [boolean, FirstLastSeenArgs] => { + const { search } = useKibana().services.data; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); - const [firstLastSeenHostRequest, setFirstLastSeenHostRequest] = - useState<HostFirstLastSeenRequestOptions>({ - defaultIndex: indexNames, - docValueFields: docValueFields ?? [], - factoryQueryType: HostsQueries.firstOrLastSeen, - hostName, - order, - }); - const [firstLastSeenHostResponse, setFirstLastSeenHostResponse] = useState<FirstLastSeenHostArgs>( - { - order: null, - firstSeen: null, - lastSeen: null, - errorMessage: null, - id: ID, - } - ); + const [firstLastSeenRequest, setFirstLastSeenRequest] = useState<FirstLastSeenRequestOptions>({ + defaultIndex, + factoryQueryType: FirstLastSeenQuery, + field, + value, + order, + }); + + const [firstLastSeenResponse, setFirstLastSeenResponse] = useState<FirstLastSeenArgs>({ + order: null, + firstSeen: null, + lastSeen: null, + errorMessage: null, + id: ID, + }); + const { addError, addWarning } = useAppToasts(); - const firstLastSeenHostSearch = useCallback( - (request: HostFirstLastSeenRequestOptions) => { + const firstLastSeenSearch = useCallback( + (request: FirstLastSeenRequestOptions) => { const asyncSearch = async () => { abortCtrl.current = new AbortController(); setLoading(true); - searchSubscription$.current = data.search - .search<HostFirstLastSeenRequestOptions, HostFirstLastSeenStrategyResponse>(request, { + searchSubscription$.current = search + .search<FirstLastSeenRequestOptions, FirstLastSeenStrategyResponse>(request, { strategy: 'securitySolutionSearchStrategy', abortSignal: abortCtrl.current.signal, }) @@ -81,7 +80,7 @@ export const useFirstLastSeenHost = ({ next: (response) => { if (isCompleteResponse(response)) { setLoading(false); - setFirstLastSeenHostResponse((prevResponse) => ({ + setFirstLastSeenResponse((prevResponse) => ({ ...prevResponse, errorMessage: null, firstSeen: response.firstSeen, @@ -96,7 +95,7 @@ export const useFirstLastSeenHost = ({ }, error: (msg) => { setLoading(false); - setFirstLastSeenHostResponse((prevResponse) => ({ + setFirstLastSeenResponse((prevResponse) => ({ ...prevResponse, errorMessage: msg, })); @@ -111,31 +110,31 @@ export const useFirstLastSeenHost = ({ abortCtrl.current.abort(); asyncSearch(); }, - [data.search, addError, addWarning] + [search, addError, addWarning] ); useEffect(() => { - setFirstLastSeenHostRequest((prevRequest) => { + setFirstLastSeenRequest((prevRequest) => { const myRequest = { ...prevRequest, - defaultIndex: indexNames, - docValueFields: docValueFields ?? [], - hostName, + defaultIndex, + field, + value, }; if (!deepEqual(prevRequest, myRequest)) { return myRequest; } return prevRequest; }); - }, [indexNames, docValueFields, hostName]); + }, [defaultIndex, field, value]); useEffect(() => { - firstLastSeenHostSearch(firstLastSeenHostRequest); + firstLastSeenSearch(firstLastSeenRequest); return () => { searchSubscription$.current.unsubscribe(); abortCtrl.current.abort(); }; - }, [firstLastSeenHostRequest, firstLastSeenHostSearch]); + }, [firstLastSeenRequest, firstLastSeenSearch]); - return [loading, firstLastSeenHostResponse]; + return [loading, firstLastSeenResponse]; }; diff --git a/x-pack/plugins/security_solution/public/common/images/cloud_security_posture_dashboard_page.png b/x-pack/plugins/security_solution/public/common/images/cloud_security_posture_dashboard_page.png new file mode 100644 index 0000000000000..de64f3be902cb Binary files /dev/null and b/x-pack/plugins/security_solution/public/common/images/cloud_security_posture_dashboard_page.png differ diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index 97befebc04d50..8a35034646e43 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -152,7 +152,10 @@ export interface UseGetUserCasesPermissions { } export const useGetUserCasesPermissions = () => { - const [casesPermissions, setCasesPermissions] = useState<UseGetUserCasesPermissions | null>(null); + const [casesPermissions, setCasesPermissions] = useState<UseGetUserCasesPermissions>({ + crud: false, + read: false, + }); const uiCapabilities = useKibana().services.application.capabilities; useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts index 02e98d099a7aa..5e34218a9f0b5 100644 --- a/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/process_actions/index.ts @@ -6,17 +6,26 @@ */ import type { - KillProcessRequestBody, + KillOrSuspendProcessRequestBody, ResponseActionApiResponse, } from '../../../../common/endpoint/types'; import { KibanaServices } from '../kibana'; -import { KILL_PROCESS_ROUTE } from '../../../../common/endpoint/constants'; +import { KILL_PROCESS_ROUTE, SUSPEND_PROCESS_ROUTE } from '../../../../common/endpoint/constants'; /** Kills a process specified by pid or entity id on a host running Endpoint Security */ -export const killProcess = async ( - params: KillProcessRequestBody +export const killProcess = ( + params: KillOrSuspendProcessRequestBody ): Promise<ResponseActionApiResponse> => { return KibanaServices.get().http.post<ResponseActionApiResponse>(KILL_PROCESS_ROUTE, { body: JSON.stringify(params), }); }; + +/** Suspends a process specified by pid or entity id on a host running Endpoint Security */ +export const suspendProcess = ( + params: KillOrSuspendProcessRequestBody +): Promise<ResponseActionApiResponse> => { + return KibanaServices.get().http.post<ResponseActionApiResponse>(SUSPEND_PROCESS_ROUTE, { + body: JSON.stringify(params), + }); +}; diff --git a/x-pack/plugins/security_solution/public/common/links/app_links.ts b/x-pack/plugins/security_solution/public/common/links/app_links.ts index fe870caa09c37..cd57f94c0407d 100644 --- a/x-pack/plugins/security_solution/public/common/links/app_links.ts +++ b/x-pack/plugins/security_solution/public/common/links/app_links.ts @@ -12,6 +12,7 @@ import { getCasesLinkItems } from '../../cases/links'; import { links as managementLinks, getManagementFilteredLinks } from '../../management/links'; import { dashboardsLandingLinks, threatHuntingLandingLinks } from '../../landing_pages/links'; import { gettingStartedLinks } from '../../overview/links'; +import { rootLinks as cloudSecurityPostureRootLinks } from '../../cloud_security_posture/links'; import { StartPlugins } from '../../types'; const casesLinks = getCasesLinkItems(); @@ -19,6 +20,7 @@ const casesLinks = getCasesLinkItems(); export const links = Object.freeze([ dashboardsLandingLinks, detectionLinks, + cloudSecurityPostureRootLinks, timelinesLinks, casesLinks, threatHuntingLandingLinks, @@ -35,6 +37,7 @@ export const getFilteredLinks = async ( return Object.freeze([ dashboardsLandingLinks, detectionLinks, + cloudSecurityPostureRootLinks, timelinesLinks, casesLinks, threatHuntingLandingLinks, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx index 5c2777febfb71..4746c759b0a2d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx @@ -35,7 +35,7 @@ export const useAddToCaseActions = ({ }: UseAddToCaseActions) => { const { cases: casesUi } = useKibana().services; const casePermissions = useGetUserCasesPermissions(); - const hasWritePermissions = casePermissions?.crud ?? false; + const hasWritePermissions = casePermissions.crud; const isAlert = useMemo(() => { return ecsData?.event?.kind?.includes('signal'); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_bulk_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_bulk_add_to_case_actions.tsx index 188cc4fdd54e1..c19f48163123e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_bulk_add_to_case_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_bulk_add_to_case_actions.tsx @@ -20,7 +20,7 @@ export const useBulkAddToCaseActions = ({ onClose, onSuccess }: UseAddToCaseActi const { cases: casesUi } = useKibana().services; const casePermissions = useGetUserCasesPermissions(); - const hasWritePermissions = casePermissions?.crud ?? false; + const hasWritePermissions = casePermissions.crud; const createCaseFlyout = casesUi.hooks.getUseCasesAddToNewCaseFlyout({ onClose, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pick_timeline/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pick_timeline/index.tsx index a047710ff5872..b1d1df13e4a74 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pick_timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pick_timeline/index.tsx @@ -36,23 +36,21 @@ export const PickTimeline = ({ useEffect(() => { const { id, title } = field.value as FieldValueTimeline; - if (timelineTitle !== title && timelineId !== id) { + if (timelineId !== id) { setTimelineId(id); setTimelineTitle(title); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [field.value]); + }, [field.value, timelineId]); const handleOnTimelineChange = useCallback( (title: string, id: string | null) => { if (id === null) { field.setValue({ id, title: null }); - } else if (timelineTitle !== title && timelineId !== id) { + } else if (timelineId !== id) { field.setValue({ id, title }); } }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [field] + [field, timelineId] ); return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx index ca17144133dbd..422282e942ddb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/related_integrations/integrations_description/integration_status_badge.tsx @@ -49,7 +49,9 @@ const IntegrationStatusBadgeComponent: React.FC<IntegrationStatusBadgeProps> = ( return ( <EuiToolTip content={badgeTooltip}> - <PaddedBadge color={badgeColor}>{badgeText}</PaddedBadge> + <PaddedBadge color={badgeColor} data-test-subj={'statusBadge'}> + {badgeText} + </PaddedBadge> </EuiToolTip> ); }; diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx deleted file mode 100644 index d598406be2594..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx +++ /dev/null @@ -1,184 +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 from 'react'; - -import { render as _render, waitFor } from '@testing-library/react'; - -import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; -import { TestProviders } from '../../../common/mock'; -import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; - -const MOCKED_RESPONSE = { - firstSeen: '2019-04-08T16:09:40.692Z', - lastSeen: '2019-04-08T18:35:45.064Z', -}; - -jest.mock('../../containers/hosts/first_last_seen'); -const useFirstLastSeenHostMock = useFirstLastSeenHost as jest.Mock; -useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); - -describe('FirstLastSeen Component', () => { - const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; - const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; - - let render: (ui: React.ReactElement) => ReturnType<typeof _render>; - - beforeEach(() => { - render = (ui: React.ReactElement): ReturnType<typeof _render> => { - return _render( - <TestProviders> - <div data-test-subj="test-render-output">{ui}</div> - </TestProviders> - ); - }; - }); - - test('Loading', async () => { - useFirstLastSeenHostMock.mockReturnValue([true, MOCKED_RESPONSE]); - const { getByTestId } = render( - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.FIRST_SEEN} - /> - ); - - // Removed strict equality as the EuiLoader has been converted to Emotion and will no longer have the euiLoadingSpinner--medium class - expect(getByTestId('test-render-output').innerHTML).toContain('<span class="euiLoadingSpinner'); - }); - - test('First Seen', async () => { - useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); - const { getByTestId } = render( - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.FIRST_SEEN} - /> - ); - - await waitFor(() => { - expect(getByTestId('test-render-output').innerHTML).toBe( - `<div class="euiText css-1vkrgyt-euiText-s"><span class="euiToolTipAnchor">${firstSeen}</span></div>` - ); - }); - }); - - test('Last Seen', async () => { - useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]); - const { getByTestId } = render( - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.LAST_SEEN} - /> - ); - await waitFor(() => { - expect(getByTestId('test-render-output').innerHTML).toBe( - `<div class="euiText css-1vkrgyt-euiText-s"><span class="euiToolTipAnchor">${lastSeen}</span></div>` - ); - }); - }); - - test('First Seen is empty but not Last Seen', async () => { - useFirstLastSeenHostMock.mockReturnValue([ - false, - { - ...MOCKED_RESPONSE, - firstSeen: null, - }, - ]); - const { getByTestId } = render( - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.LAST_SEEN} - /> - ); - - await waitFor(() => { - expect(getByTestId('test-render-output').innerHTML).toBe( - `<div class="euiText css-1vkrgyt-euiText-s"><span class="euiToolTipAnchor">${lastSeen}</span></div>` - ); - }); - }); - - test('Last Seen is empty but not First Seen', async () => { - useFirstLastSeenHostMock.mockReturnValue([ - false, - { - ...MOCKED_RESPONSE, - lastSeen: null, - }, - ]); - const { getByTestId } = render( - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.FIRST_SEEN} - /> - ); - - await waitFor(() => { - expect(getByTestId('test-render-output').innerHTML).toBe( - `<div class="euiText css-1vkrgyt-euiText-s"><span class="euiToolTipAnchor">${firstSeen}</span></div>` - ); - }); - }); - - test('First Seen With a bad date time string', async () => { - useFirstLastSeenHostMock.mockReturnValue([ - false, - { - ...MOCKED_RESPONSE, - firstSeen: 'something-invalid', - }, - ]); - const { container } = render( - <TestProviders> - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.FIRST_SEEN} - /> - </TestProviders> - ); - await waitFor(() => { - expect(container.textContent).toBe('something-invalid'); - }); - }); - - test('Last Seen With a bad date time string', async () => { - useFirstLastSeenHostMock.mockReturnValue([ - false, - { - ...MOCKED_RESPONSE, - lastSeen: 'something-invalid', - }, - ]); - const { container } = render( - <TestProviders> - <FirstLastSeenHost - docValueFields={[]} - indexNames={[]} - hostName="kibana-siem" - type={FirstLastSeenHostType.LAST_SEEN} - /> - </TestProviders> - ); - await waitFor(() => { - expect(container.textContent).toBe('something-invalid'); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx deleted file mode 100644 index 741fc7be6614f..0000000000000 --- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx +++ /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 { EuiIcon, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui'; -import React, { useMemo } from 'react'; - -import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen'; -import { getEmptyTagValue } from '../../../common/components/empty_value'; -import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date'; -import { Direction, DocValueFields } from '../../../../common/search_strategy'; - -export enum FirstLastSeenHostType { - FIRST_SEEN = 'first-seen', - LAST_SEEN = 'last-seen', -} - -interface FirstLastSeenHostProps { - docValueFields: DocValueFields[]; - hostName: string; - indexNames: string[]; - type: FirstLastSeenHostType; -} - -export const FirstLastSeenHost = React.memo<FirstLastSeenHostProps>( - ({ docValueFields, hostName, type, indexNames }) => { - const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({ - docValueFields, - hostName, - indexNames, - order: type === FirstLastSeenHostType.FIRST_SEEN ? Direction.asc : Direction.desc, - }); - const valueSeen = useMemo( - () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen), - [firstSeen, lastSeen, type] - ); - - if (errorMessage != null) { - return ( - <EuiToolTip - position="top" - content={errorMessage} - data-test-subj="firstLastSeenErrorToolTip" - aria-label={`firstLastSeenError-${type}`} - id={`firstLastSeenError-${hostName}-${type}`} - > - <EuiIcon aria-describedby={`firstLastSeenError-${hostName}-${type}`} type="alert" /> - </EuiToolTip> - ); - } - - return ( - <> - {loading && <EuiLoadingSpinner size="m" />} - {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date' - ? valueSeen - : !loading && - valueSeen != null && ( - <EuiText size="s"> - <FormattedRelativePreferenceDate value={`${valueSeen}`} /> - </EuiText> - )} - {!loading && valueSeen == null && getEmptyTagValue()} - </> - ); - } -); - -FirstLastSeenHost.displayName = 'FirstLastSeenHost'; diff --git a/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/index.tsx index bbbda683bb869..59f58d4318482 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/host_risk_score_table/index.tsx @@ -9,6 +9,7 @@ import React, { useMemo, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; +import styled from 'styled-components'; import { Columns, Criteria, @@ -46,6 +47,10 @@ export const rowItems: ItemsPerRow[] = [ }, ]; +const IconWrapper = styled.span` + margin-left: ${({ theme }) => theme.eui.euiSizeS}; +`; + const tableType = hostsModel.HostsTableType.risk; interface HostRiskScoreTableProps { @@ -150,9 +155,9 @@ const HostRiskScoreTableComponent: React.FC<HostRiskScoreTableProps> = ({ ); const headerTitle = ( - <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> - <EuiFlexItem grow={false}>{i18nHosts.HOST_RISK_TITLE}</EuiFlexItem> - <EuiFlexItem grow={false}> + <> + {i18nHosts.HOST_RISK_TITLE} + <IconWrapper> <EuiIconTip color="subdued" content={i18nHosts.HOST_RISK_TABLE_TOOLTIP} @@ -160,8 +165,8 @@ const HostRiskScoreTableComponent: React.FC<HostRiskScoreTableProps> = ({ size="l" type="iInCircle" /> - </EuiFlexItem> - </EuiFlexGroup> + </IconWrapper> + </> ); const getHostRiskScoreFilterQuerySelector = useMemo( diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index f5fdef3d5aa04..50683955fc5bc 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -157,13 +157,11 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta > {({ isLoadingAnomaliesData, anomaliesData }) => ( <HostOverviewManage - docValueFields={docValueFields} id={id} isInDetailsSidePanel={false} data={hostOverview as HostItem} anomaliesData={anomaliesData} isLoadingAnomaliesData={isLoadingAnomaliesData} - indexNames={selectedPatterns} loading={loading} startDate={from} endDate={to} @@ -179,6 +177,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta refetch={refetch} inspect={inspect} hostName={detailName} + indexNames={selectedPatterns} /> )} </AnomalyTableProvider> diff --git a/x-pack/plugins/security_solution/public/landing_pages/links.ts b/x-pack/plugins/security_solution/public/landing_pages/links.ts index e840ab7cc8716..0a53aa88208b5 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/links.ts +++ b/x-pack/plugins/security_solution/public/landing_pages/links.ts @@ -19,6 +19,7 @@ import { links as hostsLinks } from '../hosts/links'; import { links as networkLinks } from '../network/links'; import { links as usersLinks } from '../users/links'; import { links as kubernetesLinks } from '../kubernetes/links'; +import { dashboardLinks as cloudSecurityPostureLinks } from '../cloud_security_posture/links'; export const dashboardsLandingLinks: LinkItem = { id: SecurityPageName.dashboardsLanding, @@ -32,7 +33,7 @@ export const dashboardsLandingLinks: LinkItem = { defaultMessage: 'Dashboards', }), ], - links: [overviewLinks, detectionResponseLinks, kubernetesLinks], + links: [overviewLinks, detectionResponseLinks, kubernetesLinks, cloudSecurityPostureLinks], skipUrlState: true, hideTimeline: true, }; @@ -42,7 +43,7 @@ export const threatHuntingLandingLinks: LinkItem = { title: EXPLORE, path: EXPLORE_PATH, globalNavEnabled: true, - globalNavOrder: 5, + globalNavOrder: 6, capabilities: [`${SERVER_APP_ID}.show`], globalSearchKeywords: [ i18n.translate('xpack.securitySolution.appLinks.explore', { diff --git a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx index abced3257c681..88b063dd037be 100644 --- a/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx +++ b/x-pack/plugins/security_solution/public/lazy_sub_plugins.tsx @@ -25,6 +25,8 @@ import { Timelines } from './timelines'; import { Management } from './management'; import { LandingPages } from './landing_pages'; +import { CloudSecurityPosture } from './cloud_security_posture'; + /** * The classes used to instantiate the sub plugins. These are grouped into a single object for the sake of bundling them in a single dynamic import. */ @@ -42,5 +44,6 @@ const subPluginClasses = { Timelines, Management, LandingPages, + CloudSecurityPosture, }; export { subPluginClasses }; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts index 151414f078b7d..f0f4ab8e13782 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_delete_modal.test.ts @@ -15,7 +15,8 @@ import { import { act, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -describe('When displaying the Delete artfifact modal in the Artifact List Page', () => { +// FLAKY: https://github.com/elastic/kibana/issues/135794 +describe.skip('When displaying the Delete artfifact modal in the Artifact List Page', () => { let renderResult: ReturnType<AppContextTestRender['render']>; let history: AppContextTestRender['history']; let coreStart: AppContextTestRender['coreStart']; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx index a1e8c5327c93c..fb9afb3754f09 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx @@ -7,13 +7,14 @@ import React, { memo, PropsWithChildren, ReactNode, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiCode, EuiText } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; import { UnsupportedMessageCallout } from './unsupported_message_callout'; import { ParsedCommandInterface } from '../service/parsed_command_input'; import { CommandDefinition, CommandExecutionComponentProps } from '../types'; import { CommandInputUsage } from './command_usage'; import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { ConsoleCodeBlock } from './console_code_block'; export type BadArgumentProps = PropsWithChildren<{ parsedInput: ParsedCommandInterface; @@ -35,28 +36,36 @@ export const BadArgument = memo<CommandExecutionComponentProps<{}, { errorMessag return ( <UnsupportedMessageCallout header={ - <FormattedMessage - id="xpack.securitySolution.console.badArgument.title" - defaultMessage="Unsupported argument" - /> + <ConsoleCodeBlock textColor="error"> + <FormattedMessage + id="xpack.securitySolution.console.badArgument.title" + defaultMessage="Unsupported argument" + /> + </ConsoleCodeBlock> } data-test-subj={getTestId('badArgument')} > <> <div data-test-subj={getTestId('badArgument-message')}>{store.errorMessage}</div> - <div className="eui-displayInlineBlock"> + <EuiSpacer size="s" /> + <div> <CommandInputUsage commandDef={command.commandDefinition} /> </div> <div> - <EuiText size="s" className="eui-displayInlineBlock"> + <ConsoleCodeBlock> <FormattedMessage id="xpack.securitySolution.console.badArgument.helpMessage" defaultMessage="Enter {helpCmd} for further assistance." values={{ - helpCmd: <EuiCode>{`${command.commandDefinition.name} --help`}</EuiCode>, + helpCmd: ( + <ConsoleCodeBlock + bold + inline + >{`${command.commandDefinition.name} --help`}</ConsoleCodeBlock> + ), }} /> - </EuiText> + </ConsoleCodeBlock> </div> </> </UnsupportedMessageCallout> diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx new file mode 100644 index 0000000000000..e39a36de0ddfa --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.test.tsx @@ -0,0 +1,73 @@ +/* + * 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 { ConsoleProps } from '..'; +import { AppContextTestRender } from '../../../../common/mock/endpoint'; +import { getConsoleTestSetup } from '../mocks'; +import { act } from '@testing-library/react'; +import { CommandExecutionComponentProps } from '../types'; + +describe('When using CommandExecutionOutput component', () => { + let render: (props?: Partial<ConsoleProps>) => ReturnType<AppContextTestRender['render']>; + let renderResult: ReturnType<typeof render>; + let setCmd1ToComplete: () => void; + + beforeEach(() => { + const { renderConsole, commands, enterCommand } = getConsoleTestSetup(); + + const cmd1 = commands.find((command) => command.name === 'cmd1'); + + if (!cmd1) { + throw new Error('cmd1 command not found in test mocks'); + } + + (cmd1.RenderComponent as jest.Mock).mockImplementation( + (props: CommandExecutionComponentProps) => { + setCmd1ToComplete = () => props.setStatus('success'); + + return <div>{'output'}</div>; + } + ); + + render = (props = {}) => { + renderResult = renderConsole(props); + enterCommand('cmd1'); + return renderResult; + }; + }); + + it('should show long running hint message if pending and >15s have passed', () => { + jest.useFakeTimers(); + render(); + + expect(renderResult.queryByTestId('test-longRunningCommandHint')).toBeNull(); + + act(() => { + jest.advanceTimersByTime(16 * 1000); + }); + + expect(renderResult.getByTestId('test-longRunningCommandHint')).not.toBeNull(); + }); + + it('should remove long running hint message if command completes', async () => { + jest.useFakeTimers(); + render(); + + act(() => { + jest.advanceTimersByTime(16 * 1000); + }); + + expect(renderResult.getByTestId('test-longRunningCommandHint')).not.toBeNull(); + + act(() => { + setCmd1ToComplete(); + }); + + expect(renderResult.queryByTestId('test-longRunningCommandHint')).toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.tsx index 5d4641ff31777..d8134769db4c3 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_output.tsx @@ -5,9 +5,11 @@ * 2.0. */ -import React, { memo, useCallback, useMemo } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { EuiLoadingChart, EuiSpacer } from '@elastic/eui'; import styled from 'styled-components'; +import moment from 'moment'; +import { LongRunningCommandHint } from './long_running_command_hint'; import { CommandExecutionResult } from './command_execution_result'; import type { CommandExecutionComponentProps } from '../types'; import type { CommandExecutionState, CommandHistoryItem } from './console_state/types'; @@ -26,9 +28,10 @@ export interface CommandExecutionOutputProps { item: CommandHistoryItem; } export const CommandExecutionOutput = memo<CommandExecutionOutputProps>( - ({ item: { command, state, id } }) => { + ({ item: { command, state, id, enteredAt } }) => { const dispatch = useConsoleStateDispatch(); const RenderComponent = command.commandDefinition.RenderComponent; + const [isLongRunningCommand, setIsLongRunningCommand] = useState(false); const isRunning = useMemo(() => { return state.status === 'pending'; @@ -62,6 +65,30 @@ export const CommandExecutionOutput = memo<CommandExecutionOutputProps>( [dispatch, id] ); + // keep track if this becomes a long running command + useEffect(() => { + let timeoutId: ReturnType<typeof setTimeout>; + + if (isRunning && !isLongRunningCommand) { + const elapsedSeconds = moment().diff(moment(enteredAt), 'seconds'); + + if (elapsedSeconds >= 15) { + setIsLongRunningCommand(true); + return; + } + + timeoutId = setTimeout(() => { + setIsLongRunningCommand(true); + }, (15 - elapsedSeconds) * 1000); + } + + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [enteredAt, isLongRunningCommand, isRunning]); + return ( <CommandOutputContainer> <div> @@ -80,6 +107,13 @@ export const CommandExecutionOutput = memo<CommandExecutionOutputProps>( /> {isRunning && <EuiLoadingChart className="busy-indicator" mono={true} />} + + {isRunning && isLongRunningCommand && ( + <> + <EuiSpacer size="s" /> + <LongRunningCommandHint /> + </> + )} </div> </CommandOutputContainer> ); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_result.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_result.tsx index 30bed05afc0ee..1fb0b2ebe26d6 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_result.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_execution_result.tsx @@ -8,10 +8,11 @@ import React, { memo, PropsWithChildren, ComponentType, useMemo } from 'react'; import type { ReactNode } from 'react'; import { i18n } from '@kbn/i18n'; -import { CommonProps, EuiPanel, EuiSpacer, EuiText, EuiTextColor } from '@elastic/eui'; +import { CommonProps, EuiPanel, EuiSpacer } from '@elastic/eui'; import classNames from 'classnames'; import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { ConsoleText } from './console_text'; const COMMAND_EXECUTION_RESULT_SUCCESS_TITLE = i18n.translate( 'xpack.securitySolution.commandExecutionResult.successTitle', @@ -84,24 +85,19 @@ export const CommandExecutionResult = memo<CommandExecutionResultProps>( data-test-subj={dataTestSubj ? dataTestSubj : getTestId('commandExecutionResult')} > {showAs === 'pending' ? ( - <EuiText size="s"> - <EuiTextColor color="subdued"> - {children ?? COMMAND_EXECUTION_RESULT_PENDING} - </EuiTextColor> - </EuiText> + <ConsoleText>{children ?? COMMAND_EXECUTION_RESULT_PENDING}</ConsoleText> ) : ( <> {showTitle && ( <> - <EuiText size="s"> - <EuiTextColor color={showAs === 'success' ? 'success' : 'danger'}> - {title - ? title - : showAs === 'success' - ? COMMAND_EXECUTION_RESULT_SUCCESS_TITLE - : COMMAND_EXECUTION_RESULT_FAILURE_TITLE} - </EuiTextColor> - </EuiText> + <ConsoleText color={showAs === 'success' ? 'success' : 'danger'}> + {title + ? title + : showAs === 'success' + ? COMMAND_EXECUTION_RESULT_SUCCESS_TITLE + : COMMAND_EXECUTION_RESULT_FAILURE_TITLE} + </ConsoleText> + <EuiSpacer size="s" /> </> )} diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx index da52453b5abc0..3eb32e665bd97 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/command_usage.tsx @@ -6,29 +6,24 @@ */ import React, { memo, useMemo } from 'react'; -import { - EuiBadge, - EuiDescriptionList, - EuiFlexGroup, - EuiFlexItem, - EuiPanel, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiDescriptionList, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ConsoleCodeBlock } from './console_code_block'; import { getArgumentsForCommand } from '../service/parsed_command_input'; import { CommandDefinition } from '../types'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj'; +const additionalProps = { + className: 'euiTruncateText', +}; + export const CommandInputUsage = memo<Pick<CommandUsageProps, 'commandDef'>>(({ commandDef }) => { const usageHelp = useMemo(() => { return getArgumentsForCommand(commandDef).map((usage) => { return ( <EuiText size="s"> - <EuiBadge>{commandDef.name}</EuiBadge> - <ConsoleCodeBlock>{usage}</ConsoleCodeBlock> + <ConsoleCodeBlock>{`${commandDef.name} ${usage}`}</ConsoleCodeBlock> </EuiText> ); }); @@ -36,33 +31,46 @@ export const CommandInputUsage = memo<Pick<CommandUsageProps, 'commandDef'>>(({ return ( <> - <EuiFlexGroup gutterSize="s"> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.console.commandUsage.inputUsage" - defaultMessage="Usage:" - /> - </EuiText> - </EuiFlexItem> - <EuiFlexItem> - <code>{usageHelp}</code> - </EuiFlexItem> - </EuiFlexGroup> + <EuiDescriptionList + compressed + type="column" + className="descriptionList-20_80" + listItems={[ + { + title: ( + <ConsoleCodeBlock> + {i18n.translate('xpack.securitySolution.console.commandUsage.inputUsage', { + defaultMessage: 'Usage', + })} + </ConsoleCodeBlock> + ), + description: usageHelp, + }, + ]} + descriptionProps={additionalProps} + titleProps={additionalProps} + /> + <EuiSpacer size="s" /> {commandDef.exampleUsage && ( - <EuiFlexGroup gutterSize="s"> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.console.commandUsage.exampleUsage" - defaultMessage="Example:" - /> - </EuiText> - </EuiFlexItem> - <EuiFlexItem grow> - <ConsoleCodeBlock>{commandDef.exampleUsage}</ConsoleCodeBlock> - </EuiFlexItem> - </EuiFlexGroup> + <EuiDescriptionList + compressed + type="column" + className="descriptionList-20_80" + listItems={[ + { + title: ( + <ConsoleCodeBlock> + {i18n.translate('xpack.securitySolution.console.commandUsage.exampleUsage', { + defaultMessage: 'Example', + })} + </ConsoleCodeBlock> + ), + description: <ConsoleCodeBlock>{commandDef.exampleUsage}</ConsoleCodeBlock>, + }, + ]} + descriptionProps={additionalProps} + titleProps={additionalProps} + /> )} </> ); @@ -120,91 +128,81 @@ export const CommandUsage = memo<CommandUsageProps>(({ commandDef }) => { return enteredCommands; }, [commandDef.args, hasArgs]); - const additionalProps = useMemo( - () => ({ - className: 'euiTruncateText', - }), - [] - ); + const parametersDescriptionList = (title: string, parameters: CommandDetails) => { + const description = parameters.map((item) => ( + <div> + <ConsoleCodeBlock bold inline> + {item.title} + </ConsoleCodeBlock> + <ConsoleCodeBlock inline>{` - ${item.description}`}</ConsoleCodeBlock> + </div> + )); + return ( + <> + <EuiSpacer /> + {commandDef.args && ( + <EuiDescriptionList + compressed + type="column" + className="descriptionList-20_80" + listItems={[{ title: <ConsoleCodeBlock>{title}</ConsoleCodeBlock>, description }]} + descriptionProps={additionalProps} + titleProps={additionalProps} + data-test-subj={getTestId('commandUsage-options')} + /> + )} + </> + ); + }; return ( - <EuiPanel color="transparent" data-test-subj={getTestId('commandUsage')}> - <EuiText>{commandDef.about}</EuiText> + <EuiPanel paddingSize="none" color="transparent" data-test-subj={getTestId('commandUsage')}> + <EuiDescriptionList + compressed + type="column" + className="descriptionList-20_80" + listItems={[ + { + title: ( + <ConsoleCodeBlock> + {i18n.translate('xpack.securitySolution.console.commandUsage.about', { + defaultMessage: 'About', + })} + </ConsoleCodeBlock> + ), + description: <ConsoleCodeBlock>{commandDef.about}</ConsoleCodeBlock>, + }, + ]} + descriptionProps={additionalProps} + titleProps={additionalProps} + data-test-subj={getTestId('commandUsage-options')} + /> + <EuiSpacer size="s" /> <CommandInputUsage commandDef={commandDef} /> - {commandOptions.required && commandOptions.required.length > 0 && ( - <> - <EuiSpacer /> - <EuiText> - <FormattedMessage - id="xpack.securitySolution.console.commandUsage.requiredLabel" - defaultMessage="Required parameters:" - /> - {commandDef.mustHaveArgs && commandDef.args && hasArgs && ( - <EuiText size="s" color="subdued"> - <FormattedMessage - id="xpack.securitySolution.console.commandUsage.atLeastOneOptionRequiredMessage" - defaultMessage="Note: at least one option must be used" - /> - </EuiText> - )} - </EuiText> - {commandDef.args && ( - <EuiDescriptionList - compressed - type="column" - className="descriptionList-20_80" - listItems={commandOptions.required} - descriptionProps={additionalProps} - titleProps={additionalProps} - data-test-subj={getTestId('commandUsage-options')} - /> - )} - </> - )} - {commandOptions.exclusiveOr && commandOptions.exclusiveOr.length > 0 && ( - <> - <EuiSpacer /> - <EuiText> - <FormattedMessage - id="xpack.securitySolution.console.commandUsage.exclusiveOr" - defaultMessage="Include only one of the following required parameters:" - /> - </EuiText> - {commandDef.args && ( - <EuiDescriptionList - compressed - type="column" - className="descriptionList-20_80" - listItems={commandOptions.exclusiveOr} - descriptionProps={additionalProps} - titleProps={additionalProps} - data-test-subj={getTestId('commandUsage-options')} - /> - )} - </> - )} - {commandOptions.optional && commandOptions.optional.length > 0 && ( - <> - <EuiSpacer /> - <EuiText> - <FormattedMessage - id="xpack.securitySolution.console.commandUsage.optionalLabel" - defaultMessage="Optional parameters:" - /> - </EuiText> - {commandDef.args && ( - <EuiDescriptionList - compressed - type="column" - className="descriptionList-20_80" - listItems={commandOptions.optional} - descriptionProps={additionalProps} - titleProps={additionalProps} - data-test-subj={getTestId('commandUsage-options')} - /> - )} - </> - )} + {commandOptions.required && + commandOptions.required.length > 0 && + parametersDescriptionList( + i18n.translate('xpack.securitySolution.console.commandUsage.requiredLabel', { + defaultMessage: 'Required parameters', + }), + commandOptions.required + )} + {commandOptions.exclusiveOr && + commandOptions.exclusiveOr.length > 0 && + parametersDescriptionList( + i18n.translate('xpack.securitySolution.console.commandUsage.exclusiveOr', { + defaultMessage: 'Include only one parameter', + }), + commandOptions.exclusiveOr + )} + {commandOptions.optional && + commandOptions.optional.length > 0 && + parametersDescriptionList( + i18n.translate('xpack.securitySolution.console.commandUsage.optional', { + defaultMessage: 'Optional parameters', + }), + commandOptions.optional + )} </EuiPanel> ); }); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_code_block.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_code_block.tsx index f47e0932d6230..0bf12b1a5e905 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_code_block.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_code_block.tsx @@ -5,7 +5,39 @@ * 2.0. */ -import { EuiCode } from '@elastic/eui'; +import React, { memo, ReactNode } from 'react'; +import { EuiText, EuiTextColor } from '@elastic/eui'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -export const ConsoleCodeBlock = euiStyled(EuiCode).attrs({ transparentBackground: true })``; +export const ConsoleCodeBlock = memo<{ + children: ReactNode; + inline?: boolean; + textColor?: 'default' | 'error' | 'success'; + bold?: boolean; +}>(({ children, inline = false, textColor = 'default', bold = false }) => { + const baseStyledComponent = inline ? EuiTextColor : EuiText; + + const CodeBlock = euiStyled(baseStyledComponent).attrs({ + transparentBackground: true, + size: 's', + })`{ + color: ${(props) => { + if (textColor === 'error') { + return props.theme.eui.euiColorDanger; + } else if (textColor === 'success') { + return props.theme.eui.euiColorSuccessText; + } else { + return props.theme.eui.euiColorDarkestShade; + } + }}; + font-weight: ${(props) => { + return bold ? props.theme.eui.euiFontWeightBold : props.theme.eui.euiFontWeightRegular; + }}; + font-family: ${(props) => props.theme.eui.euiCodeFontFamily}; + padding: 0; + } + `; + + return <CodeBlock>{children}</CodeBlock>; +}); +ConsoleCodeBlock.displayName = 'ConsoleCodeBlock'; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.test.tsx index c464b10f5bbeb..cb5e2a387711b 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.test.tsx @@ -132,7 +132,7 @@ describe('When a Console command is entered by the user', () => { await waitFor(() => { expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( - 'The following cmd2 argument is not support by this command: --foo' + 'The following cmd2 argument is not supported by this command: --foo' ); }); }); @@ -143,7 +143,7 @@ describe('When a Console command is entered by the user', () => { await waitFor(() => { expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( - 'The following cmd2 arguments are not support by this command: --foo, --bar' + 'The following cmd2 arguments are not supported by this command: --foo, --bar' ); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx index c4072a1e9ffbb..1c2d15f8f693d 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx @@ -112,6 +112,18 @@ const cloneCommandDefinitionWithNewRenderComponent = ( }; }; +const createCommandHistoryEntry = ( + command: CommandHistoryItem['command'], + state: CommandHistoryItem['state'] = createCommandExecutionState() +): CommandHistoryItem => { + return { + id: uuidV4(), + enteredAt: new Date().toISOString(), + command, + state, + }; +}; + export const handleExecuteCommand: ConsoleStoreReducer< ConsoleDataAction & { type: 'executeCommand' } > = (state, action) => { @@ -128,18 +140,17 @@ export const handleExecuteCommand: ConsoleStoreReducer< // Unknown command if (!commandDefinition) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: { + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry({ input: parsedInput.input, args: parsedInput, commandDefinition: { ...UnknownCommandDefinition, RenderComponent: UnknownCommand, }, - }, - state: createCommandExecutionState(), - }); + }) + ); } const command = { @@ -151,102 +162,126 @@ export const handleExecuteCommand: ConsoleStoreReducer< const exclusiveOrArgs = getExclusiveOrArgs(commandDefinition.args); const exclusiveOrErrorMessage = ( - <FormattedMessage - id="xpack.securitySolution.console.commandValidation.exlcusiveOr" - defaultMessage="This command supports only one of the following arguments: {argNames}" - values={{ - argNames: ( - <ConsoleCodeBlock>{exclusiveOrArgs.map(toCliArgumentOption).join(', ')}</ConsoleCodeBlock> - ), - }} - /> + <ConsoleCodeBlock> + <FormattedMessage + id="xpack.securitySolution.console.commandValidation.exclusiveOr" + defaultMessage="This command supports only one of the following arguments: {argNames}" + values={{ + argNames: ( + <ConsoleCodeBlock bold inline> + {exclusiveOrArgs.map(toCliArgumentOption).join(', ')} + </ConsoleCodeBlock> + ), + }} + /> + </ConsoleCodeBlock> ); // If args were entered, then validate them if (parsedInput.hasArgs) { // Show command help if (parsedInput.hasArg('help')) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, HelpCommandArgument), - state: createCommandExecutionState(), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, HelpCommandArgument) + ) + ); } // Command supports no arguments if (!commandDefinition.args || Object.keys(commandDefinition.args).length === 0) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.noArgumentsSupported', - { - defaultMessage: 'Command does not support any arguments', - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: i18n.translate( + 'xpack.securitySolution.console.commandValidation.noArgumentsSupported', + { + defaultMessage: 'Command does not support any arguments', + } + ), + }) + ) + ); } // no unknown arguments allowed? const unknownInputArgs = getUnknownArguments(parsedInput.args, commandDefinition.args); if (unknownInputArgs.length) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: ( - <FormattedMessage - id="xpack.securitySolution.console.commandValidation.unknownArgument" - defaultMessage="The following {command} {countOfInvalidArgs, plural, =1 {argument is} other {arguments are}} not support by this command: {unknownArgs}" - values={{ - countOfInvalidArgs: unknownInputArgs.length, - command: <ConsoleCodeBlock>{parsedInput.name}</ConsoleCodeBlock>, - unknownArgs: ( - <ConsoleCodeBlock> - {unknownInputArgs.map(toCliArgumentOption).join(', ')} - </ConsoleCodeBlock> - ), - }} - /> - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + <FormattedMessage + id="xpack.securitySolution.console.commandValidation.unknownArgument" + defaultMessage="The following {command} {countOfInvalidArgs, plural, =1 {argument is} other {arguments are}} not supported by this command: {unknownArgs}" + values={{ + countOfInvalidArgs: unknownInputArgs.length, + command: ( + <ConsoleCodeBlock bold inline> + {parsedInput.name} + </ConsoleCodeBlock> + ), + unknownArgs: ( + <ConsoleCodeBlock bold inline> + {unknownInputArgs.map(toCliArgumentOption).join(', ')} + </ConsoleCodeBlock> + ), + }} + /> + </ConsoleCodeBlock> + ), + }) + ) + ); } // Missing required Arguments for (const requiredArg of requiredArgs) { if (!parsedInput.args[requiredArg]) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.missingRequiredArg', - { - defaultMessage: 'Missing required argument: {argName}', - values: { - argName: toCliArgumentOption(requiredArg), - }, - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + {i18n.translate( + 'xpack.securitySolution.console.commandValidation.missingRequiredArg', + { + defaultMessage: 'Missing required argument: {argName}', + values: { + argName: toCliArgumentOption(requiredArg), + }, + } + )} + </ConsoleCodeBlock> + ), + }) + ) + ); } } // Validate exclusiveOr arguments, can only have one. const exclusiveArgsUsed = exclusiveOrArgs.filter((arg) => parsedInput.args[arg]); if (exclusiveArgsUsed.length > 1) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: exclusiveOrErrorMessage, - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: exclusiveOrErrorMessage, + }) + ) + ); } // Validate each argument given to the command @@ -256,95 +291,124 @@ export const handleExecuteCommand: ConsoleStoreReducer< // Unknown argument if (!argDefinition) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.unsupportedArg', - { - defaultMessage: 'Unsupported argument: {argName}', - values: { argName: toCliArgumentOption(argName) }, - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + {i18n.translate( + 'xpack.securitySolution.console.commandValidation.unsupportedArg', + { + defaultMessage: 'Unsupported argument: {argName}', + values: { argName: toCliArgumentOption(argName) }, + } + )} + </ConsoleCodeBlock> + ), + }) + ) + ); } // does not allow multiple values if (!argDefinition.allowMultiples && Array.isArray(argInput) && argInput.length > 1) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.argSupportedOnlyOnce', - { - defaultMessage: 'Argument can only be used once: {argName}', - values: { argName: toCliArgumentOption(argName) }, - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + {i18n.translate( + 'xpack.securitySolution.console.commandValidation.argSupportedOnlyOnce', + { + defaultMessage: 'Argument can only be used once: {argName}', + values: { argName: toCliArgumentOption(argName) }, + } + )} + </ConsoleCodeBlock> + ), + }) + ) + ); } if (argDefinition.validate) { const validationResult = argDefinition.validate(argInput); if (validationResult !== true) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.invalidArgValue', - { - defaultMessage: 'Invalid argument value: {argName}. {error}', - values: { argName: toCliArgumentOption(argName), error: validationResult }, - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + {i18n.translate( + 'xpack.securitySolution.console.commandValidation.invalidArgValue', + { + defaultMessage: 'Invalid argument value: {argName}. {error}', + values: { argName: toCliArgumentOption(argName), error: validationResult }, + } + )} + </ConsoleCodeBlock> + ), + }) + ) + ); } } } } else if (requiredArgs.length > 0) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.mustHaveArgs', - { - defaultMessage: 'Missing required arguments: {requiredArgs}', - values: { - requiredArgs: requiredArgs.map((argName) => toCliArgumentOption(argName)).join(', '), - }, - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + {i18n.translate('xpack.securitySolution.console.commandValidation.mustHaveArgs', { + defaultMessage: 'Missing required arguments: {requiredArgs}', + values: { + requiredArgs: requiredArgs + .map((argName) => toCliArgumentOption(argName)) + .join(', '), + }, + })} + </ConsoleCodeBlock> + ), + }) + ) + ); } else if (exclusiveOrArgs.length > 0) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: exclusiveOrErrorMessage, - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: exclusiveOrErrorMessage, + }) + ) + ); } else if (commandDefinition.mustHaveArgs) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: i18n.translate( - 'xpack.securitySolution.console.commandValidation.oneArgIsRequired', - { - defaultMessage: 'At least one argument must be used', - } - ), - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: ( + <ConsoleCodeBlock> + {i18n.translate('xpack.securitySolution.console.commandValidation.oneArgIsRequired', { + defaultMessage: 'At least one argument must be used', + })} + </ConsoleCodeBlock> + ), + }) + ) + ); } // if the Command definition has a `validate()` callback, then call it now @@ -352,20 +416,18 @@ export const handleExecuteCommand: ConsoleStoreReducer< const validationResult = commandDefinition.validate(command); if (validationResult !== true) { - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command: cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), - state: createCommandExecutionState({ - errorMessage: validationResult, - }), - }); + return updateStateWithNewCommandHistoryItem( + state, + createCommandHistoryEntry( + cloneCommandDefinitionWithNewRenderComponent(command, BadArgument), + createCommandExecutionState({ + errorMessage: validationResult, + }) + ) + ); } } // All is good. Execute the command - return updateStateWithNewCommandHistoryItem(state, { - id: uuidV4(), - command, - state: createCommandExecutionState(), - }); + return updateStateWithNewCommandHistoryItem(state, createCommandHistoryEntry(command)); }; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts index 699f78438101e..1d1de9702f40b 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/types.ts @@ -75,6 +75,7 @@ export interface InputHistoryItem { export interface CommandHistoryItem { id: string; + enteredAt: string; command: Command; state: CommandExecutionState; } diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_text.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_text.tsx new file mode 100644 index 0000000000000..e89d9b9c3bbb8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_text.tsx @@ -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 React, { memo, PropsWithChildren } from 'react'; +import { EuiText, EuiTextColor, EuiTextColorProps, EuiTextProps, useEuiTheme } from '@elastic/eui'; + +type ConsoleTextProps = PropsWithChildren<{ + size?: EuiTextProps['size']; + color?: EuiTextColorProps['color']; + className?: string; + 'data-test-subj'?: string; +}>; + +export const ConsoleText = memo<ConsoleTextProps>( + ({ size = 's', color, children, 'data-test-subj': dataTestSubj, className }) => { + const { euiTheme } = useEuiTheme(); + + return ( + // className of `font-family-code` below is defined globally in `Console` styles + <EuiText + size={size} + data-test-subj={dataTestSubj} + className={`font-family-code ${className ?? ''}`} + > + <EuiTextColor color={color ?? euiTheme.colors.text}>{children}</EuiTextColor> + </EuiText> + ); + } +); +ConsoleText.displayName = 'ConsoleText'; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/long_running_command_hint.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/long_running_command_hint.tsx new file mode 100644 index 0000000000000..549938b675b56 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/long_running_command_hint.tsx @@ -0,0 +1,26 @@ +/* + * 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, { memo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ConsoleText } from './console_text'; +import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; + +export const LongRunningCommandHint = memo(() => { + const getTestId = useTestIdGenerator(useDataTestSubj()); + + return ( + <ConsoleText data-test-subj={getTestId('longRunningCommandHint')}> + <FormattedMessage + id="xpack.securitySolution.console.longRunningCommandHintMessage" + defaultMessage="Hint: We are still working on this task. You may leave the console or place another command while this command is processing." + /> + </ConsoleText> + ); +}); +LongRunningCommandHint.displayName = 'LongRunningCommandHint'; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/unknown_comand.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/unknown_comand.tsx index 1da0c3e24ae85..0cda295d3a244 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/unknown_comand.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/unknown_comand.tsx @@ -6,27 +6,38 @@ */ import React, { memo, useEffect, useMemo } from 'react'; -import { EuiCode, EuiIcon } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { UnsupportedMessageCallout } from './unsupported_message_callout'; import { CommandExecutionComponentProps } from '../types'; import { useDataTestSubj } from '../hooks/state_selectors/use_data_test_subj'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; +import { ConsoleCodeBlock } from './console_code_block'; export const UnknownCommand = memo<CommandExecutionComponentProps>(({ command, setStatus }) => { const getTestId = useTestIdGenerator(useDataTestSubj()); const message = useMemo(() => { return ( - <FormattedMessage - id="xpack.securitySolution.console.unknownCommand.helpMessage" - defaultMessage="The text you entered {userInput} is unsupported! Click {helpIcon} or type {helpCmd} for assistance." - values={{ - userInput: <EuiCode>{command.input}</EuiCode>, - helpIcon: <EuiIcon type="help" />, - helpCmd: <EuiCode>{'help'}</EuiCode>, - }} - /> + <ConsoleCodeBlock> + <FormattedMessage + id="xpack.securitySolution.console.unknownCommand.helpMessage" + defaultMessage="The text you entered {userInput} is unsupported! Click {helpIcon} or type {helpCmd} for assistance." + values={{ + userInput: ( + <ConsoleCodeBlock bold inline> + {command.input} + </ConsoleCodeBlock> + ), + helpIcon: <EuiIcon type="help" />, + helpCmd: ( + <ConsoleCodeBlock bold inline> + {'help'} + </ConsoleCodeBlock> + ), + }} + /> + </ConsoleCodeBlock> ); }, [command.input]); @@ -37,10 +48,12 @@ export const UnknownCommand = memo<CommandExecutionComponentProps>(({ command, s return ( <UnsupportedMessageCallout header={ - <FormattedMessage - id="xpack.securitySolution.console.unknownCommand.title" - defaultMessage="Unsupported text/command" - /> + <ConsoleCodeBlock textColor="error"> + <FormattedMessage + id="xpack.securitySolution.console.unknownCommand.title" + defaultMessage="Unsupported text/command" + /> + </ConsoleCodeBlock> } data-test-subj={getTestId('unknownCommandError')} > diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_error.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_error.tsx new file mode 100644 index 0000000000000..69e14ed169b71 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/action_error.tsx @@ -0,0 +1,31 @@ +/* + * 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, { memo } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { CommandExecutionResultComponent } from '../console/components/command_execution_result'; +import { ImmutableArray } from '../../../../common/endpoint/types'; + +export const ActionError = memo<{ + title: string; + dataTestSubj?: string; + errors: ImmutableArray<string>; + ResultComponent: CommandExecutionResultComponent; +}>(({ title, dataTestSubj, errors, ResultComponent }) => { + return ( + <ResultComponent showAs="failure" title={title} data-test-subj={dataTestSubj}> + <FormattedMessage + id="xpack.securitySolution.endpointResponseActions.actionError.errorMessage" + defaultMessage="The following errors were encountered:" + /> + <EuiSpacer size="s" /> + <div>{errors.join(' | ')}</div> + </ResultComponent> + ); +}); +ActionError.displayName = 'ActionError'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts index 9088e793b222f..e5c0af1f72e3c 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/endpoint_response_actions_console_commands.ts @@ -10,6 +10,7 @@ import { CommandDefinition } from '../console'; import { IsolateActionResult } from './isolate_action'; import { ReleaseActionResult } from './release_action'; import { KillProcessActionResult } from './kill_process_action'; +import { SuspendProcessActionResult } from './suspend_process_action'; import { EndpointStatusActionResult } from './status_action'; import { GetProcessesActionResult } from './get_processes_action'; import type { ParsedArgData } from '../console/service/parsed_command_input'; @@ -79,7 +80,7 @@ export const getEndpointResponseActionsConsoleCommands = ( meta: { endpointId: endpointAgentId, }, - exampleUsage: 'kill-process --pid 123', + exampleUsage: 'kill-process --pid 123 --comment "kill this process"', exampleInstruction: 'Enter a pid or an entity id to execute', mustHaveArgs: true, args: { @@ -116,6 +117,55 @@ export const getEndpointResponseActionsConsoleCommands = ( }, }, }, + { + name: 'suspend-process', + about: i18n.translate('xpack.securitySolution.endpointConsoleCommands.suspendProcess.about', { + defaultMessage: 'Suspend a running process', + }), + RenderComponent: SuspendProcessActionResult, + meta: { + endpointId: endpointAgentId, + }, + exampleUsage: 'suspend-process --pid 123 --comment "suspend this process"', + exampleInstruction: 'Enter a pid or an entity id to execute', + mustHaveArgs: true, + args: { + comment: { + required: false, + allowMultiples: false, + about: i18n.translate( + 'xpack.securitySolution.endpointConsoleCommands.suspendProcess.arg.comment', + { defaultMessage: 'A comment to go along with the action' } + ), + }, + pid: { + required: false, + allowMultiples: false, + exclusiveOr: true, + about: i18n.translate( + 'xpack.securitySolution.endpointConsoleCommands.suspendProcess.pid.arg.comment', + { + defaultMessage: + 'A PID representing the process to suspend. You can enter a pid or an entity id, but not both.', + } + ), + validate: emptyArgumentValidator, + }, + entityId: { + required: false, + allowMultiples: false, + exclusiveOr: true, + about: i18n.translate( + 'xpack.securitySolution.endpointConsoleCommands.suspendProcess.entityId.arg.comment', + { + defaultMessage: + 'An entity id representing the process to suspend. You can enter a pid or an entity id, but not both.', + } + ), + validate: emptyArgumentValidator, + }, + }, + }, { name: 'status', about: i18n.translate('xpack.securitySolution.endpointConsoleCommands.status.about', { diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx index 6aeb6219a7b19..c19e2995ebeb0 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/get_processes_action.tsx @@ -16,6 +16,7 @@ import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details import { EndpointCommandDefinitionMeta } from './types'; import { CommandExecutionComponentProps } from '../console/types'; import { useSendGetEndpointProcessesRequest } from '../../hooks/endpoint/use_send_get_endpoint_processes_request'; +import { ActionError } from './action_error'; // @ts-expect-error TS2769 const StyledEuiBasicTable = styled(EuiBasicTable)` @@ -191,20 +192,15 @@ export const GetProcessesActionResult = memo< // Show errors if (completedActionDetails?.errors) { return ( - <ResultComponent - showAs="failure" + <ActionError title={i18n.translate( 'xpack.securitySolution.endpointResponseActions.getProcesses.errorMessageTitle', { defaultMessage: 'Get processes action failed' } )} - data-test-subj="getProcessesErrorCallout" - > - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.getProcesses.errorMessage" - defaultMessage="The following errors were encountered: {errors}" - values={{ errors: completedActionDetails.errors.join(' | ') }} - /> - </ResultComponent> + dataTestSubj={'getProcessesErrorCallout'} + errors={completedActionDetails?.errors} + ResultComponent={ResultComponent} + /> ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx index d18c49c87d64f..312ca69bc5b29 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/isolate_action.tsx @@ -7,12 +7,12 @@ import React, { memo, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { ActionDetails } from '../../../../common/endpoint/types'; import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; import { EndpointCommandDefinitionMeta } from './types'; import { useSendIsolateEndpointRequest } from '../../hooks/endpoint/use_send_isolate_endpoint_request'; import { CommandExecutionComponentProps } from '../console/types'; +import { ActionError } from './action_error'; export const IsolateActionResult = memo< CommandExecutionComponentProps< @@ -80,20 +80,15 @@ export const IsolateActionResult = memo< // Show errors if (completedActionDetails?.errors) { return ( - <ResultComponent - showAs="failure" + <ActionError title={i18n.translate( 'xpack.securitySolution.endpointResponseActions.isolate.errorMessageTitle', { defaultMessage: 'Error. Isolate action failed.' } )} - data-test-subj="isolateErrorCallout" - > - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.isolate.errorMessage" - defaultMessage="The following errors were encountered: {errors}" - values={{ errors: completedActionDetails.errors.join(' | ') }} - /> - </ResultComponent> + dataTestSubj={'isolateErrorCallout'} + errors={completedActionDetails?.errors} + ResultComponent={ResultComponent} + /> ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx index 5cb2944082657..e1dbc922bcdb1 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/kill_process_action.tsx @@ -14,6 +14,7 @@ import type { EndpointCommandDefinitionMeta } from './types'; import { useSendKillProcessRequest } from '../../hooks/endpoint/use_send_kill_process_endpoint_request'; import type { CommandExecutionComponentProps } from '../console/types'; import { parsedPidOrEntityIdParameter } from '../console/service/parsed_command_input'; +import { ActionError } from './action_error'; export const KillProcessActionResult = memo< CommandExecutionComponentProps< @@ -96,20 +97,15 @@ export const KillProcessActionResult = memo< // Show errors if (completedActionDetails?.errors) { return ( - <ResultComponent - showAs="failure" + <ActionError title={i18n.translate( 'xpack.securitySolution.endpointResponseActions.killProcess.errorMessageTitle', { defaultMessage: 'Kill process action failure' } )} - data-test-subj="killProcessErrorCallout" - > - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.killProcess.errorMessage" - defaultMessage="The following errors were encountered: {errors}" - values={{ errors: completedActionDetails.errors.join(' | ') }} - /> - </ResultComponent> + dataTestSubj={'killProcessErrorCallout'} + errors={completedActionDetails?.errors} + ResultComponent={ResultComponent} + /> ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx index d4afe0a4ff796..c65bbb2beffee 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/release_action.tsx @@ -7,12 +7,12 @@ import React, { memo, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { ActionDetails } from '../../../../common/endpoint/types'; import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; import { EndpointCommandDefinitionMeta } from './types'; import { useSendReleaseEndpointRequest } from '../../hooks/endpoint/use_send_release_endpoint_request'; import { CommandExecutionComponentProps } from '../console/types'; +import { ActionError } from './action_error'; export const ReleaseActionResult = memo< CommandExecutionComponentProps< @@ -80,20 +80,15 @@ export const ReleaseActionResult = memo< // Show errors if (completedActionDetails?.errors) { return ( - <ResultComponent - showAs="failure" + <ActionError title={i18n.translate( 'xpack.securitySolution.endpointResponseActions.release.errorMessageTitle', { defaultMessage: 'Error. Release action failed.' } )} - data-test-subj="releaseErrorCallout" - > - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.release.errorMessage" - defaultMessage="The following errors were encountered: {errors}" - values={{ errors: completedActionDetails.errors.join(' | ') }} - /> - </ResultComponent> + dataTestSubj={'releaseErrorCallout'} + errors={completedActionDetails?.errors} + ResultComponent={ResultComponent} + /> ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/status_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/status_action.tsx index 9b907738fb504..9941b57253776 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/status_action.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/status_action.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useEffect, useMemo } from 'react'; -import { EuiCallOut, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import type { HttpFetchError } from '@kbn/core/public'; @@ -33,21 +33,25 @@ export const EndpointStatusActionResult = memo< }, EndpointCommandDefinitionMeta > ->(({ command, status, setStatus, store, setStore }) => { +>(({ command, status, setStatus, store, setStore, ResultComponent }) => { const endpointId = command.commandDefinition?.meta?.endpointId as string; const { endpointPendingActions, endpointDetails, detailsFetchError, apiCalled } = store; const isPending = status === 'pending'; + const queryKey = useMemo(() => { + return uuidV4(); + }, []); const { + data: fetchedEndpointDetails, + error: fetchedDetailsError, isFetching, isFetched, - refetch: fetchEndpointDetails, - } = useGetEndpointDetails(endpointId, { enabled: false }); + } = useGetEndpointDetails(endpointId, { enabled: isPending, queryKey }); - const { refetch: fetchEndpointPendingActionsSummary } = useGetEndpointPendingActionsSummary( - [endpointId], - { enabled: false } - ); + const { data: fetchedPendingActionsSummary } = useGetEndpointPendingActionsSummary([endpointId], { + enabled: isPending, + queryKey, + }); const pendingIsolationActions = useMemo< Pick<Required<EndpointHostIsolationStatusProps>, 'pendingIsolate' | 'pendingUnIsolate'> @@ -67,123 +71,118 @@ export const EndpointStatusActionResult = memo< }, [endpointPendingActions?.data]); useEffect(() => { - if (!apiCalled) { + if (!isPending) { setStore((prevState) => { return { ...prevState, apiCalled: true, }; }); - - // Using a unique `queryKey` here and below so that data is NOT updated - // from cache when future requests for this endpoint ID is done again. - fetchEndpointDetails({ queryKey: uuidV4() }) - .then(({ data }) => { - setStore((prevState) => { - return { - ...prevState, - endpointDetails: data, - }; - }); - }) - .catch((err) => { - setStore((prevState) => { - return { - ...prevState, - detailsFetchError: err, - }; - }); - }); - - fetchEndpointPendingActionsSummary({ queryKey: uuidV4() }).then(({ data }) => { - setStore((prevState) => { - return { - ...prevState, - endpointPendingActions: data, - }; - }); - }); } - }, [apiCalled, fetchEndpointDetails, fetchEndpointPendingActionsSummary, setStore]); + }, [apiCalled, isPending, setStore]); + // update command store if endpoint details fetch api call completed useEffect(() => { if (isFetched && isPending) { setStatus(detailsFetchError ? 'error' : 'success'); + setStore((prevState) => { + return { + ...prevState, + endpointDetails: fetchedEndpointDetails, + detailsFetchError: fetchedDetailsError ?? undefined, + }; + }); } - }, [detailsFetchError, isFetched, setStatus, isPending]); + }, [ + detailsFetchError, + isFetched, + setStatus, + isPending, + setStore, + fetchedEndpointDetails, + fetchedDetailsError, + ]); - if (isFetching) { - return null; - } + // Update the store once we get back pending actions for this endpoint + useEffect(() => { + if (fetchedPendingActionsSummary) { + setStore((prevState) => { + return { + ...prevState, + endpointPendingActions: fetchedPendingActionsSummary, + }; + }); + } + }, [fetchedPendingActionsSummary, setStore]); if (detailsFetchError) { return ( - <EuiCallOut> - <EuiFieldText> - <FormattedError error={detailsFetchError} /> - </EuiFieldText> - </EuiCallOut> + <ResultComponent showAs="failure"> + <FormattedError error={detailsFetchError} /> + </ResultComponent> ); } - if (!endpointDetails) { - return null; + if (isFetching || !endpointDetails) { + return <ResultComponent showAs="pending" />; } return ( - <EuiFlexGroup wrap={false} responsive={false}> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.status.agentStatus" - defaultMessage="Agent status" - /> - </EuiText> - <EndpointAgentAndIsolationStatus - status={endpointDetails.host_status} - isIsolated={Boolean(endpointDetails.metadata.Endpoint.state?.isolation)} - {...pendingIsolationActions} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.status.version" - defaultMessage="Version" - /> - </EuiText> - <EuiText>{endpointDetails.metadata.agent.version}</EuiText> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.status.policyStatus" - defaultMessage="Policy status" - /> - </EuiText> - <EndpointAppliedPolicyStatus - policyApplied={endpointDetails.metadata.Endpoint.policy.applied} - /> - </EuiFlexItem> - <EuiFlexItem> - <EuiText size="s"> - <FormattedMessage - id="xpack.securitySolution.endpointResponseActions.status.lastActive" - defaultMessage="Last active" + <ResultComponent showTitle={false}> + <EuiFlexGroup wrap={false} responsive={false}> + <EuiFlexItem grow={false}> + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.endpointResponseActions.status.agentStatus" + defaultMessage="Agent status" + /> + </EuiText> + <EndpointAgentAndIsolationStatus + status={endpointDetails.host_status} + isIsolated={Boolean(endpointDetails.metadata.Endpoint.state?.isolation)} + {...pendingIsolationActions} /> - </EuiText> - <EuiText> - <FormattedDate - fieldName={i18n.translate( - 'xpack.securitySolution.endpointResponseActions.status.lastActive', - { defaultMessage: 'Last active' } - )} - value={endpointDetails.metadata['@timestamp']} - className="eui-textTruncate" + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.endpointResponseActions.status.version" + defaultMessage="Version" + /> + </EuiText> + <EuiText>{endpointDetails.metadata.agent.version}</EuiText> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.endpointResponseActions.status.policyStatus" + defaultMessage="Policy status" + /> + </EuiText> + <EndpointAppliedPolicyStatus + policyApplied={endpointDetails.metadata.Endpoint.policy.applied} /> - </EuiText> - </EuiFlexItem> - </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiText size="s"> + <FormattedMessage + id="xpack.securitySolution.endpointResponseActions.status.lastActive" + defaultMessage="Last active" + /> + </EuiText> + <EuiText> + <FormattedDate + fieldName={i18n.translate( + 'xpack.securitySolution.endpointResponseActions.status.lastActive', + { defaultMessage: 'Last active' } + )} + value={endpointDetails.metadata['@timestamp']} + className="eui-textTruncate" + /> + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + </ResultComponent> ); }); EndpointStatusActionResult.displayName = 'EndpointStatusActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx new file mode 100644 index 0000000000000..1b3cf93bd8e1d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.test.tsx @@ -0,0 +1,238 @@ +/* + * 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 { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import { + ConsoleManagerTestComponent, + getConsoleManagerMockRenderResultQueriesAndActions, +} from '../console/components/console_manager/mocks'; +import React from 'react'; +import { getEndpointResponseActionsConsoleCommands } from './endpoint_response_actions_console_commands'; +import { enterConsoleCommand } from '../console/mocks'; +import { waitFor } from '@testing-library/react'; +import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; + +describe('When using the suspend-process action from response actions console', () => { + let render: () => Promise<ReturnType<AppContextTestRender['render']>>; + let renderResult: ReturnType<AppContextTestRender['render']>; + let apiMocks: ReturnType<typeof responseActionsHttpMocks>; + let consoleManagerMockAccess: ReturnType< + typeof getConsoleManagerMockRenderResultQueriesAndActions + >; + + beforeEach(() => { + const mockedContext = createAppRootMockRenderer(); + + apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http); + + render = async () => { + renderResult = mockedContext.render( + <ConsoleManagerTestComponent + registerConsoleProps={() => { + return { + consoleProps: { + 'data-test-subj': 'test', + commands: getEndpointResponseActionsConsoleCommands('a.b.c'), + }, + }; + }} + /> + ); + + consoleManagerMockAccess = getConsoleManagerMockRenderResultQueriesAndActions(renderResult); + + await consoleManagerMockAccess.clickOnRegisterNewConsole(); + await consoleManagerMockAccess.openRunningConsole(); + + return renderResult; + }; + }); + + it('should call `suspend-process` api when command is entered', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123'); + + await waitFor(() => { + expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledTimes(1); + }); + }); + + it('should accept an optional `--comment`', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123 --comment "This is a comment"'); + + await waitFor(() => { + expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.stringContaining('This is a comment'), + }) + ); + }); + }); + + it('should only accept one `--comment`', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123 --comment "one" --comment "two"'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Argument can only be used once: --comment' + ); + }); + + it('should only accept one exclusive argument', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123 --entityId 123wer'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'This command supports only one of the following arguments: --pid, --entityId' + ); + }); + + it('should check for at least one exclusive argument', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'This command supports only one of the following arguments: --pid, --entityId' + ); + }); + + it('should check the pid has a given value', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Invalid argument value: --pid. Argument cannot be empty' + ); + }); + + it('should check the pid has a non-empty value', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid " "'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Invalid argument value: --pid. Argument cannot be empty' + ); + }); + + it('should check the entityId has a given value', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --entityId'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Invalid argument value: --entityId. Argument cannot be empty' + ); + }); + + it('should check the entity id has a non-empty value', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --entityId " "'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Invalid argument value: --entityId. Argument cannot be empty' + ); + }); + + it('should call the action status api after creating the `suspend-process` request', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123'); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalled(); + }); + }); + + it('should show success when `suspend-process` action completes with no errors when using `pid`', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123'); + + await waitFor(() => { + expect(renderResult.getByTestId('suspendProcessSuccessCallout')).toBeTruthy(); + }); + }); + + it('should show success when `suspend-process` action completes with no errors when using `entityId`', async () => { + await render(); + enterConsoleCommand(renderResult, 'suspend-process --entityId 123wer'); + + await waitFor(() => { + expect(renderResult.getByTestId('suspendProcessSuccessCallout')).toBeTruthy(); + }); + }); + + it('should show error if suspend-process failed to complete successfully', async () => { + const pendingDetailResponse = apiMocks.responseProvider.actionDetails({ + path: '/api/endpoint/action/1.2.3', + }); + pendingDetailResponse.data.wasSuccessful = false; + pendingDetailResponse.data.errors = ['error one', 'error two']; + apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse); + await render(); + enterConsoleCommand(renderResult, 'suspend-process --pid 123'); + + await waitFor(() => { + expect(renderResult.getByTestId('suspendProcessErrorCallout').textContent).toMatch( + /error one \| error two/ + ); + }); + }); + + describe('and when console is closed (not terminated) and then reopened', () => { + beforeEach(() => { + const _render = render; + + render = async () => { + const response = await _render(); + enterConsoleCommand(response, 'suspend-process --pid 123'); + + await waitFor(() => { + expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledTimes(1); + }); + + // Hide the console + await consoleManagerMockAccess.hideOpenedConsole(); + + return response; + }; + }); + + it('should NOT send the `suspend-process` request again', async () => { + await render(); + await consoleManagerMockAccess.openRunningConsole(); + + expect(apiMocks.responseProvider.suspendProcess).toHaveBeenCalledTimes(1); + }); + + it('should continue to check action status when still pending', async () => { + const pendingDetailResponse = apiMocks.responseProvider.actionDetails({ + path: '/api/endpoint/action/1.2.3', + }); + pendingDetailResponse.data.isCompleted = false; + apiMocks.responseProvider.actionDetails.mockReturnValue(pendingDetailResponse); + await render(); + + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(2); + + await consoleManagerMockAccess.openRunningConsole(); + + await waitFor(() => { + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(3); + }); + }); + + it('should display completion output if done (no additional API calls)', async () => { + await render(); + + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(1); + + await consoleManagerMockAccess.openRunningConsole(); + + expect(apiMocks.responseProvider.actionDetails).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx new file mode 100644 index 0000000000000..b18060a3d2776 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/suspend_process_action.tsx @@ -0,0 +1,117 @@ +/* + * 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, { memo, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { ActionDetails } from '../../../../common/endpoint/types'; +import { useGetActionDetails } from '../../hooks/endpoint/use_get_action_details'; +import type { EndpointCommandDefinitionMeta } from './types'; +import { useSendSuspendProcessRequest } from '../../hooks/endpoint/use_send_suspend_process_endpoint_request'; +import type { CommandExecutionComponentProps } from '../console/types'; +import { parsedPidOrEntityIdParameter } from '../console/service/parsed_command_input'; +import { ActionError } from './action_error'; + +export const SuspendProcessActionResult = memo< + CommandExecutionComponentProps< + { comment?: string; pid?: number; entityId?: string }, + { + actionId?: string; + actionRequestSent?: boolean; + completedActionDetails?: ActionDetails; + }, + EndpointCommandDefinitionMeta + > +>(({ command, setStore, store, status, setStatus, ResultComponent }) => { + const endpointId = command.commandDefinition?.meta?.endpointId; + const { actionId, completedActionDetails } = store; + const isPending = status === 'pending'; + const actionRequestSent = Boolean(store.actionRequestSent); + + const { mutate, data, isSuccess, error } = useSendSuspendProcessRequest(); + + const { data: actionDetails } = useGetActionDetails(actionId ?? '-', { + enabled: Boolean(actionId) && isPending, + refetchInterval: isPending ? 3000 : false, + }); + + // Send Suspend request if not yet done + useEffect(() => { + const parameters = parsedPidOrEntityIdParameter(command.args.args); + + if (!actionRequestSent && endpointId && parameters) { + mutate({ + endpoint_ids: [endpointId], + comment: command.args.args?.comment?.[0], + parameters, + }); + setStore((prevState) => { + return { ...prevState, actionRequestSent: true }; + }); + } + }, [actionRequestSent, command.args.args, endpointId, mutate, setStore]); + + // If suspend-process request was created, store the action id if necessary + useEffect(() => { + if (isSuccess && actionId !== data.data.id) { + setStore((prevState) => { + return { ...prevState, actionId: data.data.id }; + }); + } + }, [actionId, data?.data.id, isSuccess, error, setStore]); + + useEffect(() => { + if (actionDetails?.data.isCompleted) { + setStatus('success'); + setStore((prevState) => { + return { + ...prevState, + completedActionDetails: actionDetails.data, + }; + }); + } + }, [actionDetails?.data, setStatus, setStore]); + + // Show nothing if still pending + if (isPending) { + return ( + <ResultComponent showAs="pending"> + <FormattedMessage + id="xpack.securitySolution.endpointResponseActions.suspendProcess.pendingMessage" + defaultMessage="Suspending process" + /> + </ResultComponent> + ); + } + + // Show errors + if (completedActionDetails?.errors) { + return ( + <ActionError + title={i18n.translate( + 'xpack.securitySolution.endpointResponseActions.suspendProcess.errorMessageTitle', + { defaultMessage: 'Suspend process action failure' } + )} + dataTestSubj={'suspendProcessErrorCallout'} + errors={completedActionDetails?.errors} + ResultComponent={ResultComponent} + /> + ); + } + + // Show Success + return ( + <ResultComponent + title={i18n.translate( + 'xpack.securitySolution.endpointResponseActions.suspendProcess.successMessageTitle', + { defaultMessage: 'Process suspended successfully' } + )} + data-test-subj="suspendProcessSuccessCallout" + /> + ); +}); +SuspendProcessActionResult.displayName = 'SuspendProcessActionResult'; diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_list_date_range_picker.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_list_date_range_picker.tsx index ba09160a4d6da..cf453e2070705 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_list_date_range_picker.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/action_list_date_range_picker.tsx @@ -5,33 +5,31 @@ * 2.0. */ -import React, { memo, useState, useMemo } from 'react'; -import dateMath from '@kbn/datemath'; -import { EuiSuperDatePicker } from '@elastic/eui'; +import React, { useCallback, memo, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSuperDatePicker, EuiSuperUpdateButton } from '@elastic/eui'; +import type { IDataPluginServices } from '@kbn/data-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import type { EuiSuperDatePickerRecentRange } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { DurationRange, OnRefreshChangeProps } from '@elastic/eui/src/components/date_picker/types'; -import { useUiSetting$ } from '../../../../common/lib/kibana'; -import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../../../common/constants'; +import type { + DurationRange, + OnRefreshChangeProps, +} from '@elastic/eui/src/components/date_picker/types'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import type { useGetEndpointActionList } from '../../../hooks'; +import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; export interface DateRangePickerValues { autoRefreshOptions: { enabled: boolean; duration: number; }; - startDate?: string; - endDate?: string; + startDate: string; + endDate: string; recentlyUsedDateRanges: EuiSuperDatePickerRecentRange[]; } -interface Range { - from: string; - to: string; - display: string; -} const DatePickerWrapper = euiStyled.div` - width: ${(props) => props.theme.eui.fractions.single.percentage}; padding-bottom: ${(props) => `${props.theme.eui.euiCodeBlockPaddingModifiers.paddingLarge}`}; `; @@ -42,56 +40,64 @@ export const ActionListDateRangePicker = memo( onRefresh, onRefreshChange, onTimeChange, + onClick, }: { dateRangePickerState: DateRangePickerValues; isDataLoading: boolean; onRefresh: () => void; onRefreshChange: (evt: OnRefreshChangeProps) => void; onTimeChange: ({ start, end }: DurationRange) => void; + onClick: ReturnType<typeof useGetEndpointActionList>['refetch']; }) => { - const { uiSettings } = useKibana().services; - const [quickRanges] = useUiSetting$<Range[]>(DEFAULT_TIMEPICKER_QUICK_RANGES); - const [dateFormat] = useState(() => uiSettings?.get('dateFormat')); - const commonlyUsedRanges = !quickRanges.length - ? [] - : quickRanges.map(({ from, to, display }) => ({ - start: from, - end: to, - label: display, - })); - - const end = useMemo( - () => - dateRangePickerState.endDate - ? dateMath.parse(dateRangePickerState.endDate)?.toISOString() - : undefined, - [dateRangePickerState] - ); - - const start = useMemo( - () => - dateRangePickerState.startDate - ? dateMath.parse(dateRangePickerState.startDate)?.toISOString() - : undefined, - [dateRangePickerState] - ); + const getTestId = useTestIdGenerator('response-actions-list'); + const kibana = useKibana<IDataPluginServices>(); + const { uiSettings } = kibana.services; + const [commonlyUsedRanges] = useState(() => { + return ( + uiSettings + ?.get(UI_SETTINGS.TIMEPICKER_QUICK_RANGES) + ?.map(({ from, to, display }: { from: string; to: string; display: string }) => { + return { + start: from, + end: to, + label: display, + }; + }) ?? [] + ); + }); + const onClickCallback = useCallback(() => onClick(), [onClick]); return ( - <DatePickerWrapper data-test-subj="actionListSuperDatePicker"> - <EuiSuperDatePicker - updateButtonProps={{ iconOnly: true, fill: false }} - isLoading={isDataLoading} - dateFormat={dateFormat} - commonlyUsedRanges={commonlyUsedRanges} - end={end} - isPaused={!dateRangePickerState.autoRefreshOptions.enabled} - onTimeChange={onTimeChange} - onRefreshChange={onRefreshChange} - refreshInterval={dateRangePickerState.autoRefreshOptions.duration} - onRefresh={onRefresh} - recentlyUsedRanges={dateRangePickerState.recentlyUsedDateRanges} - start={start} - /> + <DatePickerWrapper data-test-subj={getTestId('super-date-picker')}> + <EuiFlexGroup alignItems="center" direction="row" responsive={false} gutterSize="s"> + <EuiFlexItem> + <EuiSuperDatePicker + isLoading={isDataLoading} + dateFormat={uiSettings.get('dateFormat')} + commonlyUsedRanges={commonlyUsedRanges} + end={dateRangePickerState.endDate} + isPaused={!dateRangePickerState.autoRefreshOptions.enabled} + onTimeChange={onTimeChange} + onRefreshChange={onRefreshChange} + refreshInterval={dateRangePickerState.autoRefreshOptions.duration} + onRefresh={onRefresh} + recentlyUsedRanges={dateRangePickerState.recentlyUsedDateRanges} + start={dateRangePickerState.startDate} + showUpdateButton={false} + updateButtonProps={{ iconOnly: true, fill: false }} + width="auto" + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiSuperUpdateButton + data-test-subj={getTestId('super-date-picker-refresh-button')} + fill={false} + iconOnly + isLoading={isDataLoading} + onClick={onClickCallback} + /> + </EuiFlexItem> + </EuiFlexGroup> </DatePickerWrapper> ); } diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx index 4cc2e79981123..5ea37b444dad1 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import dateMath from '@kbn/datemath'; import { useCallback, useState } from 'react'; import type { DurationRange, @@ -18,7 +17,7 @@ const defaultDateRangeOptions = Object.freeze({ enabled: false, duration: 10000, }, - startDate: 'now-1d', + startDate: 'now-24h/h', endDate: 'now', recentlyUsedDateRanges: [], }); @@ -31,8 +30,8 @@ export const useDateRangePicker = () => { ({ start, end }) => { setDateRangePickerState((prevState) => ({ ...prevState, - startDate: dateMath.parse(start)?.toISOString(), - endDate: dateMath.parse(end)?.toISOString(), + startDate: start, + endDate: end, })); }, [setDateRangePickerState] @@ -62,6 +61,10 @@ export const useDateRangePicker = () => { // handle manual time change on date picker const onTimeChange = useCallback( ({ start: newStart, end: newEnd }: DurationRange) => { + // update date ranges + updateActionListDateRanges({ start: newStart, end: newEnd }); + + // update recently used date ranges const newRecentlyUsedDateRanges = [ { start: newStart, end: newEnd }, ...dateRangePickerState.recentlyUsedDateRanges @@ -71,10 +74,6 @@ export const useDateRangePicker = () => { ) .slice(0, 9), ]; - - // update date ranges - updateActionListDateRanges({ start: newStart, end: newEnd }); - // update recently used date ranges updateActionListRecentlyUsedDateRanges(newRecentlyUsedDateRanges); }, [ diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.test.tsx index d3f8b780df77e..7123ead68d096 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.test.tsx @@ -9,12 +9,11 @@ import uuid from 'uuid'; import React from 'react'; import * as reactTestingLibrary from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint'; +import type { AppContextTestRender } from '../../../common/mock/endpoint'; +import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { ResponseActionsList } from './response_actions_list'; -import { ActionDetails, ActionListApiResponse } from '../../../../common/endpoint/types'; -import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; -import { createUseUiSetting$Mock } from '../../../common/lib/kibana/kibana_react.mock'; -import { DEFAULT_TIMEPICKER_QUICK_RANGES, MANAGEMENT_PATH } from '../../../../common/constants'; +import type { ActionDetails, ActionListApiResponse } from '../../../../common/endpoint/types'; +import { MANAGEMENT_PATH } from '../../../../common/constants'; import { EndpointActionGenerator } from '../../../../common/endpoint/data_generators/endpoint_action_generator'; let mockUseGetEndpointActionList: { @@ -32,60 +31,79 @@ jest.mock('../../hooks/endpoint/use_get_endpoint_action_list', () => { }; }); -const mockUseUiSetting$ = useUiSetting$ as jest.Mock; -const timepickerRanges = [ - { - from: 'now/d', - to: 'now/d', - display: 'Today', - }, - { - from: 'now/w', - to: 'now/w', - display: 'This week', - }, - { - from: 'now-15m', - to: 'now', - display: 'Last 15 minutes', - }, - { - from: 'now-30m', - to: 'now', - display: 'Last 30 minutes', - }, - { - from: 'now-1h', - to: 'now', - display: 'Last 1 hour', - }, - { - from: 'now-24h', - to: 'now', - display: 'Last 24 hours', - }, - { - from: 'now-7d', - to: 'now', - display: 'Last 7 days', - }, - { - from: 'now-30d', - to: 'now', - display: 'Last 30 days', - }, - { - from: 'now-90d', - to: 'now', - display: 'Last 90 days', - }, - { - from: 'now-1y', - to: 'now', - display: 'Last 1 year', - }, -]; -jest.mock('../../../common/lib/kibana'); +jest.mock('@kbn/kibana-react-plugin/public', () => { + const original = jest.requireActual('@kbn/kibana-react-plugin/public'); + return { + ...original, + useKibana: () => ({ + services: { + uiSettings: { + get: jest.fn().mockImplementation((key) => { + const get = (k: 'dateFormat' | 'timepicker:quickRanges') => { + const x = { + dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', + 'timepicker:quickRanges': [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, + ], + }; + return x[k]; + }; + return get(key); + }), + }, + }, + }), + }; +}); describe('Response Actions List', () => { const testPrefix = 'response-actions-list'; @@ -113,14 +131,6 @@ describe('Response Actions List', () => { reactTestingLibrary.act(() => { history.push(`${MANAGEMENT_PATH}/response_actions`); }); - (useKibana as jest.Mock).mockReturnValue({ services: mockedContext.startServices }); - mockUseUiSetting$.mockImplementation((key, defaultValue) => { - const useUiSetting$Mock = createUseUiSetting$Mock(); - - return key === DEFAULT_TIMEPICKER_QUICK_RANGES - ? [timepickerRanges, jest.fn()] - : useUiSetting$Mock(key, defaultValue); - }); mockUseGetEndpointActionList = { ...baseMockedActionList, @@ -132,16 +142,13 @@ describe('Response Actions List', () => { mockUseGetEndpointActionList = { ...baseMockedActionList, }; - }); - - afterEach(() => { jest.clearAllMocks(); }); - describe('Without agentIds filter', () => { + describe('Without data', () => { it('should show date filters', () => { render(); - expect(renderResult.getByTestId('actionListSuperDatePicker')).toBeTruthy(); + expect(renderResult.getByTestId(`${testPrefix}-super-date-picker`)).toBeTruthy(); }); it('should show empty state when there is no data', async () => { @@ -152,7 +159,9 @@ describe('Response Actions List', () => { render(); expect(renderResult.getByTestId(`${testPrefix}-empty-prompt`)).toBeTruthy(); }); + }); + describe('With Data', () => { it('should show table when there is data', async () => { render(); @@ -171,7 +180,7 @@ describe('Response Actions List', () => { ) .slice(0, 6) .map((col) => col.textContent) - ).toEqual(['Time', 'Command/action', 'User', 'Host', 'Comments', 'Status']); + ).toEqual(['Time', 'Command', 'User', 'Host', 'Comments', 'Status']); }); it('should paginate table when there is data', async () => { @@ -253,8 +262,8 @@ describe('Response Actions List', () => { it('should refresh data when autoRefresh is toggled on', async () => { render(); - const quickMenu = renderResult.getByTestId('superDatePickerToggleQuickMenuButton'); - userEvent.click(quickMenu); + const quickMenuButton = renderResult.getByTestId('superDatePickerToggleQuickMenuButton'); + userEvent.click(quickMenuButton); const toggle = renderResult.getByTestId('superDatePickerToggleRefreshButton'); const intervalInput = renderResult.getByTestId('superDatePickerRefreshIntervalInput'); @@ -266,6 +275,30 @@ describe('Response Actions List', () => { expect(refetchFunction).toHaveBeenCalledTimes(3); }); }); + + it('should refresh data when super date picker refresh button is clicked', async () => { + render(); + + const superRefreshButton = renderResult.getByTestId( + `${testPrefix}-super-date-picker-refresh-button` + ); + userEvent.click(superRefreshButton); + expect(refetchFunction).toHaveBeenCalledTimes(1); + }); + + it('should set date picker with relative dates', async () => { + render(); + const quickMenuButton = renderResult.getByTestId('superDatePickerToggleQuickMenuButton'); + const startDatePopoverButton = renderResult.getByTestId(`superDatePickerShowDatesButton`); + + // shows 24 hours at first + expect(startDatePopoverButton).toHaveTextContent('Last 24 hours'); + + // pick another relative date + userEvent.click(quickMenuButton); + userEvent.click(renderResult.getByTestId('superDatePickerCommonlyUsed_Last_15 minutes')); + expect(startDatePopoverButton).toHaveTextContent('Last 15 minutes'); + }); }); describe('Action status ', () => { @@ -357,7 +390,7 @@ describe('Response Actions List', () => { ) .slice(0, 5) .map((col) => col.textContent) - ).toEqual(['Time', 'Command/action', 'User', 'Comments', 'Status']); + ).toEqual(['Time', 'Command', 'User', 'Comments', 'Status']); }); it('should show a host column when multiple agentIds', async () => { @@ -374,7 +407,7 @@ describe('Response Actions List', () => { ) .slice(0, 6) .map((col) => col.textContent) - ).toEqual(['Time', 'Command/action', 'User', 'Host', 'Comments', 'Status']); + ).toEqual(['Time', 'Command', 'User', 'Host', 'Comments', 'Status']); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.tsx index 65e7dc13b739d..74c52f1be973b 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_list.tsx @@ -6,7 +6,6 @@ */ import { - CriteriaWithPagination, EuiAvatar, EuiBadge, EuiBasicTable, @@ -27,12 +26,12 @@ import { } from '@elastic/eui'; import { euiStyled, css } from '@kbn/kibana-react-plugin/common'; -import type { HorizontalAlignment } from '@elastic/eui'; +import type { HorizontalAlignment, CriteriaWithPagination } from '@elastic/eui'; import React, { memo, useCallback, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { getEmptyValue } from '../../../common/components/empty_value'; import { FormattedDate } from '../../../common/components/formatted_date'; -import { ActionDetails } from '../../../../common/endpoint/types'; +import type { ActionDetails } from '../../../../common/endpoint/types'; import type { EndpointActionListRequestQuery } from '../../../../common/endpoint/schema/actions'; import { ManagementEmptyStateWrapper } from '../management_empty_state_wrapper'; import { useGetEndpointActionList } from '../../hooks'; @@ -507,6 +506,7 @@ export const ResponseActionsList = memo< <ActionListDateRangePicker dateRangePickerState={dateRangePickerState} isDataLoading={isFetching} + onClick={reFetchEndpointActionList} onRefresh={onRefresh} onRefreshChange={onRefreshChange} onTimeChange={onTimeChange} diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx index a4535cad2dfe1..0fadb24a0fd0c 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/translations.tsx @@ -73,7 +73,7 @@ export const TABLE_COLUMN_NAMES = Object.freeze({ defaultMessage: 'Time', }), command: i18n.translate('xpack.securitySolution.responseActionsList.list.command', { - defaultMessage: 'Command/action', + defaultMessage: 'Command', }), user: i18n.translate('xpack.securitySolution.responseActionsList.list.user', { defaultMessage: 'User', diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_kill_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_kill_process_endpoint_request.ts index f3051890871ee..d194dd50724bb 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_kill_process_endpoint_request.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_kill_process_endpoint_request.ts @@ -8,7 +8,7 @@ import { useMutation, UseMutationOptions, UseMutationResult } from 'react-query'; import { HttpFetchError } from '@kbn/core/public'; import type { - KillProcessRequestBody, + KillOrSuspendProcessRequestBody, ResponseActionApiResponse, } from '../../../../common/endpoint/types'; import { killProcess } from '../../../common/lib/process_actions'; @@ -21,11 +21,15 @@ export const useSendKillProcessRequest = ( customOptions?: UseMutationOptions< ResponseActionApiResponse, HttpFetchError, - KillProcessRequestBody + KillOrSuspendProcessRequestBody > -): UseMutationResult<ResponseActionApiResponse, HttpFetchError, KillProcessRequestBody> => { - return useMutation<ResponseActionApiResponse, HttpFetchError, KillProcessRequestBody>( - (processData: KillProcessRequestBody) => { +): UseMutationResult< + ResponseActionApiResponse, + HttpFetchError, + KillOrSuspendProcessRequestBody +> => { + return useMutation<ResponseActionApiResponse, HttpFetchError, KillOrSuspendProcessRequestBody>( + (processData: KillOrSuspendProcessRequestBody) => { return killProcess(processData); }, customOptions diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_suspend_process_endpoint_request.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_suspend_process_endpoint_request.ts new file mode 100644 index 0000000000000..ba345789eef65 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_send_suspend_process_endpoint_request.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 { useMutation } from 'react-query'; +import type { UseMutationOptions, UseMutationResult } from 'react-query'; +import type { HttpFetchError } from '@kbn/core/public'; +import type { + KillOrSuspendProcessRequestBody, + ResponseActionApiResponse, +} from '../../../../common/endpoint/types'; +import { suspendProcess } from '../../../common/lib/process_actions'; + +/** + * Create kill process requests + * @param customOptions + */ +export const useSendSuspendProcessRequest = ( + customOptions?: UseMutationOptions< + ResponseActionApiResponse, + HttpFetchError, + KillOrSuspendProcessRequestBody + > +): UseMutationResult< + ResponseActionApiResponse, + HttpFetchError, + KillOrSuspendProcessRequestBody +> => { + return useMutation<ResponseActionApiResponse, HttpFetchError, KillOrSuspendProcessRequestBody>( + (processData: KillOrSuspendProcessRequestBody) => { + return suspendProcess(processData); + }, + customOptions + ); +}; diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index cdbdb50e9edd4..cf7e30793ba7f 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -37,7 +37,10 @@ import { import { licenseService } from '../common/hooks/use_license'; import { LinkItem } from '../common/links/types'; import { StartPlugins } from '../types'; - +import { + manageCategories as cloudSecurityPostureCategories, + manageLinks as cloudSecurityPostureLinks, +} from '../cloud_security_posture/links'; import { IconBlocklist } from './icons/blocklist'; import { IconEndpoints } from './icons/endpoints'; import { IconEndpointPolicies } from './icons/endpoint_policies'; @@ -68,6 +71,7 @@ const categories = [ SecurityPageName.blocklist, ], }, + ...cloudSecurityPostureCategories, ]; export const links: LinkItem = { @@ -77,7 +81,7 @@ export const links: LinkItem = { skipUrlState: true, hideTimeline: true, globalNavEnabled: true, - globalNavOrder: 6, + globalNavOrder: 7, capabilities: [`${SERVER_APP_ID}.show`], globalSearchKeywords: [ i18n.translate('xpack.securitySolution.appLinks.manage', { @@ -197,6 +201,7 @@ export const links: LinkItem = { skipUrlState: true, hideTimeline: true, }, + cloudSecurityPostureLinks, ], }; diff --git a/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts b/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts index 25d5b9e01207d..8f1751009041a 100644 --- a/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts +++ b/x-pack/plugins/security_solution/public/management/mocks/response_actions_http_mocks.ts @@ -15,6 +15,7 @@ import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE, KILL_PROCESS_ROUTE, + SUSPEND_PROCESS_ROUTE, } from '../../../common/endpoint/constants'; import { httpHandlerMockFactory, @@ -36,6 +37,8 @@ export type ResponseActionsHttpMocksInterface = ResponseProvidersInterface<{ killProcess: () => ActionDetailsApiResponse; + suspendProcess: () => ActionDetailsApiResponse; + actionDetails: (options: HttpFetchOptionsWithPath) => ActionDetailsApiResponse; actionList: (options: HttpFetchOptionsWithPath) => ActionListApiResponse; @@ -72,6 +75,16 @@ export const responseActionsHttpMocks = httpHandlerMockFactory<ResponseActionsHt return { data: response }; }, }, + { + id: 'suspendProcess', + path: SUSPEND_PROCESS_ROUTE, + method: 'post', + handler: (): ActionDetailsApiResponse => { + const generator = new EndpointActionGenerator('seed'); + const response = generator.generateActionDetails() as ActionDetails; + return { data: response }; + }, + }, { id: 'actionDetails', path: ACTION_DETAILS_ROUTE, diff --git a/x-pack/plugins/security_solution/public/network/components/details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/details/__snapshots__/index.test.tsx.snap index c22c3bf680781..191c22f1e7d3a 100644 --- a/x-pack/plugins/security_solution/public/network/components/details/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/network/components/details/__snapshots__/index.test.tsx.snap @@ -140,6 +140,7 @@ exports[`IP Overview Component rendering it renders the default IP Overview 1`] endDate="2019-06-18T06:00:00.000Z" flowTarget="source" id="ipOverview" + indexPatterns={Array []} ip="10.10.10.10" isInDetailsSidePanel={false} isLoadingAnomaliesData={false} @@ -291,6 +292,7 @@ exports[`IP Overview Component rendering it renders the side panel IP overview 1 endDate="2019-06-18T06:00:00.000Z" flowTarget="source" id="ipOverview" + indexPatterns={Array []} ip="10.10.10.10" isInDetailsSidePanel={true} isLoadingAnomaliesData={false} diff --git a/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx index 2bb52c679ded1..3ae735e1cef19 100644 --- a/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx +++ b/x-pack/plugins/security_solution/public/network/components/details/index.test.tsx @@ -53,6 +53,7 @@ describe('IP Overview Component', () => { updateFlowTargetAction: jest.fn() as unknown as ActionCreator<{ flowTarget: FlowTargetSourceDest; }>, + indexPatterns: [], }; test('it renders the default IP Overview', () => { 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 81e483861cc98..a4ccde6e592b5 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 @@ -20,13 +20,16 @@ import { getEmptyTagValue } from '../../../common/components/empty_value'; import { autonomousSystemRenderer, - dateRenderer, hostIdRenderer, hostNameRenderer, locationRenderer, reputationRenderer, whoisRenderer, } from '../../../timelines/components/field_renderers/field_renderers'; +import { + FirstLastSeen, + FirstLastSeenType, +} from '../../../common/components/first_last_seen/first_last_seen'; import * as i18n from './translations'; import { OverviewWrapper } from '../../../common/components/page'; import { Loader } from '../../../common/components/loader'; @@ -52,6 +55,7 @@ export interface IpOverviewProps { narrowDateRange: NarrowDateRange; startDate: string; type: networkModel.NetworkType; + indexPatterns: string[]; } export const IpOverview = React.memo<IpOverviewProps>( @@ -69,6 +73,7 @@ export const IpOverview = React.memo<IpOverviewProps>( isLoadingAnomaliesData, anomaliesData, narrowDateRange, + indexPatterns, }) => { const capabilities = useMlCapabilities(); const userPermissions = hasMlUserPermissions(capabilities); @@ -115,11 +120,25 @@ export const IpOverview = React.memo<IpOverviewProps>( [ { title: i18n.FIRST_SEEN, - description: typeData ? dateRenderer(typeData.firstSeen) : getEmptyTagValue(), + description: ( + <FirstLastSeen + indexPatterns={indexPatterns} + field={`${flowTarget}.ip`} + value={ip} + type={FirstLastSeenType.FIRST_SEEN} + /> + ), }, { title: i18n.LAST_SEEN, - description: typeData ? dateRenderer(typeData.lastSeen) : getEmptyTagValue(), + description: ( + <FirstLastSeen + indexPatterns={indexPatterns} + field={`${flowTarget}.ip`} + value={ip} + type={FirstLastSeenType.LAST_SEEN} + /> + ), }, ], [ diff --git a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx index 9752878dbf91e..2828d7cedc6dc 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/details/index.tsx @@ -168,6 +168,7 @@ const NetworkDetailsComponent: React.FC = () => { startDate={from} endDate={to} narrowDateRange={narrowDateRange} + indexPatterns={selectedPatterns} /> <EuiHorizontalRule /> diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap index 2ba013744ca10..540a36debf280 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/__snapshots__/index.test.tsx.snap @@ -192,7 +192,6 @@ exports[`Host Summary Component rendering it renders the default Host Summary 1` }, } } - docValueFields={Array []} endDate="2019-06-18T06:00:00.000Z" hostName="testHostName" id="hostOverview" @@ -397,7 +396,6 @@ exports[`Host Summary Component rendering it renders the panel view Host Summary }, } } - docValueFields={Array []} endDate="2019-06-18T06:00:00.000Z" hostName="testHostName" id="hostOverview" diff --git a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx index d928cc77d73df..6c57ea18a109f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/host_overview/index.test.tsx @@ -31,7 +31,6 @@ describe('Host Summary Component', () => { const mockProps = { anomaliesData: mockAnomalies, data: mockData.Hosts.edges[0].node, - docValueFields: [], endDate: '2019-06-18T06:00:00.000Z', id: 'hostOverview', indexNames: [], 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 5c1d6a62df5bc..c5f264e4ae9ed 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 @@ -10,12 +10,7 @@ import { euiLightVars as lightTheme, euiDarkVars as darkTheme } from '@kbn/ui-th import { getOr } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; -import { - buildHostNamesFilter, - DocValueFields, - HostItem, - RiskSeverity, -} from '../../../../common/search_strategy'; +import { buildHostNamesFilter, HostItem, RiskSeverity } from '../../../../common/search_strategy'; import { DEFAULT_DARK_MODE } from '../../../../common/constants'; import { DescriptionList } from '../../../../common/utility_types'; import { useUiSetting$ } from '../../../common/lib/kibana'; @@ -24,6 +19,10 @@ import { DefaultFieldRenderer, hostIdRenderer, } from '../../../timelines/components/field_renderers/field_renderers'; +import { + FirstLastSeen, + FirstLastSeenType, +} from '../../../common/components/first_last_seen/first_last_seen'; import { InspectButton, InspectButtonContainer } from '../../../common/components/inspect'; import { Loader } from '../../../common/components/loader'; import { NetworkDetailsLink } from '../../../common/components/links'; @@ -32,11 +31,6 @@ import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_ca import { AnomalyScores } from '../../../common/components/ml/score/anomaly_scores'; import { Anomalies, NarrowDateRange } from '../../../common/components/ml/types'; import { DescriptionListStyled, OverviewWrapper } from '../../../common/components/page'; -import { - FirstLastSeenHost, - FirstLastSeenHostType, -} from '../../../hosts/components/first_last_seen_host'; - import * as i18n from './translations'; import { EndpointOverview } from './endpoint_overview'; import { OverviewDescriptionList } from '../../../common/components/overview_description_list'; @@ -46,7 +40,6 @@ import { RiskScore } from '../../../common/components/severity/common'; interface HostSummaryProps { contextID?: string; // used to provide unique draggable context when viewing in the side panel data: HostItem; - docValueFields: DocValueFields[]; id: string; isDraggable?: boolean; isInDetailsSidePanel: boolean; @@ -70,7 +63,6 @@ export const HostOverview = React.memo<HostSummaryProps>( anomaliesData, contextID, data, - docValueFields, endDate, id, isDraggable = false, @@ -142,34 +134,28 @@ export const HostOverview = React.memo<HostSummaryProps>( }, { title: i18n.FIRST_SEEN, - description: - data && data.host != null && data.host.name && data.host.name.length ? ( - <FirstLastSeenHost - docValueFields={docValueFields} - hostName={data.host.name[0]} - indexNames={indexNames} - type={FirstLastSeenHostType.FIRST_SEEN} - /> - ) : ( - getEmptyTagValue() - ), + description: ( + <FirstLastSeen + indexPatterns={indexNames} + field={'host.name'} + value={hostName} + type={FirstLastSeenType.FIRST_SEEN} + /> + ), }, { title: i18n.LAST_SEEN, - description: - data && data.host != null && data.host.name && data.host.name.length ? ( - <FirstLastSeenHost - docValueFields={docValueFields} - hostName={data.host.name[0]} - indexNames={indexNames} - type={FirstLastSeenHostType.LAST_SEEN} - /> - ) : ( - getEmptyTagValue() - ), + description: ( + <FirstLastSeen + indexPatterns={indexNames} + field={'host.name'} + value={hostName} + type={FirstLastSeenType.LAST_SEEN} + /> + ), }, ], - [data, docValueFields, indexNames, isDraggable] + [data, indexNames, hostName, isDraggable] ); const firstColumn = useMemo( () => diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx index 2019811570888..4e40832a2d718 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx @@ -14,10 +14,11 @@ const MAX_CASES_TO_SHOW = 3; const RecentCasesComponent = () => { const { cases } = useKibana().services; - const userCanCrud = useGetUserCasesPermissions()?.crud ?? false; + const permissions = useGetUserCasesPermissions(); + const casesPermissions = { all: permissions.crud, read: permissions.read }; return cases.ui.getRecentCases({ - userCanCrud, + permissions: casesPermissions, maxCasesToShow: MAX_CASES_TO_SHOW, owner: [APP_ID], }); diff --git a/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx b/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx index f8a0e2e7c83b9..bde783afc3a2e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/sidebar/sidebar.tsx @@ -41,7 +41,7 @@ export const Sidebar = React.memo<{ ); // only render the recently created cases view if the user has at least read permissions - const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; + const hasCasesReadPermissions = useGetUserCasesPermissions().read; return ( <EuiFlexGroup direction="column" responsive={false} gutterSize="l"> diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/overview/components/user_overview/__snapshots__/index.test.tsx.snap index 66b76495b7e63..7c23423839f36 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/__snapshots__/index.test.tsx.snap @@ -139,7 +139,6 @@ exports[`User Summary Component rendering it renders the default User Summary 1` } data={ Object { - "firstSeen": undefined, "host": Object { "ip": Array [ "10.142.0.7", @@ -154,7 +153,6 @@ exports[`User Summary Component rendering it renders the default User Summary 1` ], }, }, - "lastSeen": undefined, "user": Object { "domain": Array [ "domain", @@ -170,6 +168,7 @@ exports[`User Summary Component rendering it renders the default User Summary 1` } endDate="2019-06-18T06:00:00.000Z" id="userOverview" + indexPatterns={Array []} isInDetailsSidePanel={false} isLoadingAnomaliesData={false} loading={false} @@ -318,7 +317,6 @@ exports[`User Summary Component rendering it renders the panel view User Summary } data={ Object { - "firstSeen": undefined, "host": Object { "ip": Array [ "10.142.0.7", @@ -333,7 +331,6 @@ exports[`User Summary Component rendering it renders the panel view User Summary ], }, }, - "lastSeen": undefined, "user": Object { "domain": Array [ "domain", @@ -349,6 +346,7 @@ exports[`User Summary Component rendering it renders the panel view User Summary } endDate="2019-06-18T06:00:00.000Z" id="userOverview" + indexPatterns={Array []} isInDetailsSidePanel={true} isLoadingAnomaliesData={false} loading={false} diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.test.tsx index a937575076458..c07fc5c5735b9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.test.tsx @@ -42,8 +42,6 @@ describe('User Summary Component', () => { name: ['Debian GNU/Linux'], }, }, - lastSeen: undefined, - firstSeen: undefined, }, endDate: '2019-06-18T06:00:00.000Z', id: 'userOverview', @@ -53,6 +51,7 @@ describe('User Summary Component', () => { narrowDateRange: jest.fn(), startDate: '2019-06-15T06:00:00.000Z', userName: 'testUserName', + indexPatterns: [], }; test('it renders the default User Summary', () => { diff --git a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx index c098bc47cb545..0ddcb2ea63de9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/user_overview/index.tsx @@ -15,10 +15,11 @@ import { DEFAULT_DARK_MODE } from '../../../../common/constants'; import { DescriptionList } from '../../../../common/utility_types'; import { useUiSetting$ } from '../../../common/lib/kibana'; import { getEmptyTagValue } from '../../../common/components/empty_value'; +import { DefaultFieldRenderer } from '../../../timelines/components/field_renderers/field_renderers'; import { - dateRenderer, - DefaultFieldRenderer, -} from '../../../timelines/components/field_renderers/field_renderers'; + FirstLastSeen, + FirstLastSeenType, +} from '../../../common/components/first_last_seen/first_last_seen'; import { InspectButton, InspectButtonContainer } from '../../../common/components/inspect'; import { Loader } from '../../../common/components/loader'; import { NetworkDetailsLink } from '../../../common/components/links'; @@ -48,6 +49,7 @@ export interface UserSummaryProps { endDate: string; narrowDateRange: NarrowDateRange; userName: string; + indexPatterns: string[]; } const UserRiskOverviewWrapper = styled(EuiFlexGroup)` @@ -69,6 +71,7 @@ export const UserOverview = React.memo<UserSummaryProps>( startDate, endDate, userName, + indexPatterns, }) => { const capabilities = useMlCapabilities(); const userPermissions = hasMlUserPermissions(capabilities); @@ -169,11 +172,25 @@ export const UserOverview = React.memo<UserSummaryProps>( [ { title: i18n.FIRST_SEEN, - description: data ? dateRenderer(data.firstSeen) : getEmptyTagValue(), + description: ( + <FirstLastSeen + indexPatterns={indexPatterns} + field={'user.name'} + value={userName} + type={FirstLastSeenType.FIRST_SEEN} + /> + ), }, { title: i18n.LAST_SEEN, - description: data ? dateRenderer(data.lastSeen) : getEmptyTagValue(), + description: ( + <FirstLastSeen + indexPatterns={indexPatterns} + field={'user.name'} + value={userName} + type={FirstLastSeenType.LAST_SEEN} + /> + ), }, ], [ @@ -200,7 +217,7 @@ export const UserOverview = React.memo<UserSummaryProps>( }, ], ], - [data, getDefaultRenderer, contextID, isDraggable, firstColumn] + [data, indexPatterns, getDefaultRenderer, contextID, isDraggable, userName, firstColumn] ); return ( <> diff --git a/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx b/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx index 0112de7612fe5..ee5f2400b1a95 100644 --- a/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx +++ b/x-pack/plugins/security_solution/public/overview/pages/detection_response.tsx @@ -53,7 +53,7 @@ const DetectionResponseComponent = () => { const { indicesExist, indexPattern, loading: isSourcererLoading } = useSourcererDataView(); const { signalIndexName } = useSignalIndex(); const { hasKibanaREAD, hasIndexRead } = useAlertsPrivileges(); - const canReadCases = useGetUserCasesPermissions()?.read; + const canReadCases = useGetUserCasesPermissions().read; const canReadAlerts = hasKibanaREAD && hasIndexRead; if (!canReadAlerts && !canReadCases) { diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 72ba80e182547..b9633c49849e5 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -323,6 +323,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S timelines: new subPluginClasses.Timelines(), management: new subPluginClasses.Management(), landingPages: new subPluginClasses.LandingPages(), + cloudSecurityPosture: new subPluginClasses.CloudSecurityPosture(), }; } return this._subPlugins; @@ -350,6 +351,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S kubernetes: subPlugins.kubernetes.start(), management: subPlugins.management.start(core, plugins), landingPages: subPlugins.landingPages.start(), + cloudSecurityPosture: subPlugins.cloudSecurityPosture.start(), }; } /** diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx index b4466a3640a90..565acd1dbea22 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useKibana } from '../../../../common/lib/kibana'; +import { useKibana, useGetUserCasesPermissions } from '../../../../common/lib/kibana'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { mockTimelineModel, TestProviders } from '../../../../common/mock'; import { AddToCaseButton } from '.'; @@ -35,6 +35,13 @@ jest.mock('react-redux', () => { }); jest.mock('../../../../common/lib/kibana'); +const originalKibanaLib = jest.requireActual('../../../../common/lib/kibana'); + +// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object +// The returned permissions object will indicate that the user does not have permissions by default +const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock; +mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions); + jest.mock('../../../../common/hooks/use_selector'); const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx index b48361202e54b..736fb23e4b8a0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx @@ -68,6 +68,7 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { ); const userPermissions = useGetUserCasesPermissions(); + const casesPermissions = { all: userPermissions.crud, read: userPermissions.read }; const handleButtonClick = useCallback(() => { setPopover((currentIsOpen) => !currentIsOpen); @@ -163,8 +164,8 @@ const AddToCaseButtonComponent: React.FC<Props> = ({ timelineId }) => { {isCaseModalOpen && cases.ui.getAllCasesSelectorModal({ onRowClick, - userCanCrud: userPermissions?.crud ?? false, owner: [APP_ID], + permissions: casesPermissions, })} </> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index ad2204f43178b..c81d90bae6254 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -348,10 +348,11 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r class="euiDescriptionList__description" > <span - class="c3" - > - — - </span> + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + data-test-subj="loading-spinner" + role="progressbar" + /> </dd> <dt class="euiDescriptionList__title" @@ -362,10 +363,11 @@ exports[`Details Panel Component DetailsPanel:HostDetails: rendering it should r class="euiDescriptionList__description" > <span - class="c3" - > - — - </span> + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + data-test-subj="loading-spinner" + role="progressbar" + /> </dd> </dl> </div> @@ -707,10 +709,11 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul class="euiDescriptionList__description" > <span - class="c3" - > - — - </span> + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + data-test-subj="loading-spinner" + role="progressbar" + /> </dd> <dt class="euiDescriptionList__title" @@ -721,10 +724,11 @@ exports[`Details Panel Component DetailsPanel:NetworkDetails: rendering it shoul class="euiDescriptionList__description" > <span - class="c3" - > - — - </span> + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + data-test-subj="loading-spinner" + role="progressbar" + /> </dd> </dl> </div> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx index 944d061c9cca2..3471b65a73b34 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx @@ -13,7 +13,11 @@ import { TimelineId } from '../../../../../../common/types/timeline'; import { Ecs } from '../../../../../../common/ecs'; import { mockAlertDetailsData } from '../../../../../common/components/event_details/__mocks__'; import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; -import { KibanaServices, useKibana } from '../../../../../common/lib/kibana'; +import { + KibanaServices, + useKibana, + useGetUserCasesPermissions, +} from '../../../../../common/lib/kibana'; import { coreMock } from '@kbn/core/public/mocks'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; @@ -64,7 +68,15 @@ jest.mock('../../../../../common/hooks/use_experimental_features', () => ({ jest.mock('../../../../../detections/components/user_info', () => ({ useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]), })); + jest.mock('../../../../../common/lib/kibana'); +const originalKibanaLib = jest.requireActual('../../../../../common/lib/kibana'); + +// Restore the useGetUserCasesPermissions so the calling functions can receive a valid permissions object +// The returned permissions object will indicate that the user does not have permissions by default +const mockUseGetUserCasesPermissions = useGetUserCasesPermissions as jest.Mock; +mockUseGetUserCasesPermissions.mockImplementation(originalKibanaLib.useGetUserCasesPermissions); + jest.mock( '../../../../../detections/containers/detection_engine/alerts/use_alerts_privileges', () => ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap index 7c19bd07a7267..8f14c778cff20 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap @@ -116,10 +116,11 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re class="euiDescriptionList__description" > <span - class="c3" - > - — - </span> + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + data-test-subj="loading-spinner" + role="progressbar" + /> </dd> <dt class="euiDescriptionList__title" @@ -130,10 +131,11 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re class="euiDescriptionList__description" > <span - class="c3" - > - — - </span> + aria-label="Loading" + class="euiLoadingSpinner emotion-euiLoadingSpinner-m" + data-test-subj="loading-spinner" + role="progressbar" + /> </dd> </dl> </div> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx index f19a63a5f3382..a5d735be571d3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/expandable_host.tsx @@ -64,7 +64,7 @@ export const ExpandableHostDetails = ({ Otherwise, an empty array is defaulted for the `indexNames` in the query which leads to inconsistencies in the data returned (i.e. extraneous endpoint data is retrieved from the backend leading to endpoint data not being returned) */ - const { docValueFields, selectedPatterns } = useSourcererDataView(); + const { selectedPatterns } = useSourcererDataView(); const [loading, { hostDetails: hostOverview }] = useHostDetails({ endDate: to, @@ -82,7 +82,6 @@ export const ExpandableHostDetails = ({ {({ isLoadingAnomaliesData, anomaliesData }) => ( <HostOverview contextID={contextID} - docValueFields={docValueFields} id={ID} isInDetailsSidePanel data={hostOverview as HostItem} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx index e49b1a2491878..8f66719e42635 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/network_details/expandable_network.tsx @@ -138,6 +138,7 @@ export const ExpandableNetworkDetails = ({ startDate={from} endDate={to} narrowDateRange={narrowDateRange} + indexPatterns={selectedPatterns} /> ) : ( <LandingCards /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx index 2f46cc41a3642..f1f32802845b0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/user_details/expandable_user.tsx @@ -94,6 +94,7 @@ export const ExpandableUserDetails = ({ to: fromTo.to, }); }} + indexPatterns={selectedPatterns} /> )} </AnomalyTableProvider> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx index 509e1b09929b4..0ddc739982072 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.test.tsx @@ -33,33 +33,37 @@ jest.mock( }) ); -jest.mock('../../../../../common/lib/kibana', () => ({ - useKibana: () => ({ - services: { - application: { - navigateToApp: jest.fn(), - getUrlForApp: jest.fn(), - capabilities: { - siem: { crud_alerts: true, read_alerts: true }, +jest.mock('../../../../../common/lib/kibana', () => { + const originalKibanaLib = jest.requireActual('../../../../../common/lib/kibana'); + + return { + useKibana: () => ({ + services: { + application: { + navigateToApp: jest.fn(), + getUrlForApp: jest.fn(), + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, }, + cases: mockCasesContract(), + uiSettings: { + get: jest.fn(), + }, + savedObjects: { + client: {}, + }, + timelines: { ...mockTimelines }, }, - cases: mockCasesContract(), - uiSettings: { - get: jest.fn(), - }, - savedObjects: { - client: {}, - }, - timelines: { ...mockTimelines }, - }, - }), - useToasts: jest.fn().mockReturnValue({ - addError: jest.fn(), - addSuccess: jest.fn(), - addWarning: jest.fn(), - }), - useGetUserCasesPermissions: jest.fn(), -})); + }), + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), + useGetUserCasesPermissions: originalKibanaLib.useGetUserCasesPermissions, + }; +}); const defaultProps = { ariaRowindex: 2, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 59b331d4d7f11..6d329034650f3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -39,29 +39,33 @@ jest.mock('../../../../../common/components/user_privileges', () => { }; }); -jest.mock('../../../../../common/lib/kibana', () => ({ - useKibana: () => ({ - services: { - timelines: { ...mockTimelines }, - data: { - search: jest.fn(), - query: jest.fn(), - }, - application: { - capabilities: { - siem: { crud_alerts: true, read_alerts: true }, +jest.mock('../../../../../common/lib/kibana', () => { + const originalModule = jest.requireActual('../../../../../common/lib/kibana'); + + return { + useKibana: () => ({ + services: { + timelines: { ...mockTimelines }, + data: { + search: jest.fn(), + query: jest.fn(), }, + application: { + capabilities: { + siem: { crud_alerts: true, read_alerts: true }, + }, + }, + cases: mockCasesContract(), }, - cases: mockCasesContract(), - }, - }), - useToasts: jest.fn().mockReturnValue({ - addError: jest.fn(), - addSuccess: jest.fn(), - addWarning: jest.fn(), - }), - useGetUserCasesPermissions: jest.fn(), -})); + }), + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), + useGetUserCasesPermissions: originalModule.useGetUserCasesPermissions, + }; +}); describe('EventColumnView', () => { useIsExperimentalFeatureEnabledMock.mockReturnValue(false); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index f2045327a42f7..f87baf3fd055a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -34,7 +34,6 @@ import { TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; import { createStore, State } from '../../../../common/store'; -jest.mock('../../../../common/lib/kibana/hooks'); jest.mock('../../../../common/hooks/use_app_toasts'); jest.mock('../../../../common/components/user_privileges', () => { return { @@ -84,7 +83,6 @@ jest.mock('../../../../common/lib/kibana', () => { }, }, }), - useGetUserSavedObjectPermissions: jest.fn(), }; }); diff --git a/x-pack/plugins/security_solution/public/timelines/links.ts b/x-pack/plugins/security_solution/public/timelines/links.ts index 263070c292ec1..7b18b11f6bf7b 100644 --- a/x-pack/plugins/security_solution/public/timelines/links.ts +++ b/x-pack/plugins/security_solution/public/timelines/links.ts @@ -15,7 +15,7 @@ export const links: LinkItem = { title: TIMELINES, path: TIMELINES_PATH, globalNavEnabled: true, - globalNavOrder: 3, + globalNavOrder: 4, capabilities: [`${SERVER_APP_ID}.show`], globalSearchKeywords: [ i18n.translate('xpack.securitySolution.appLinks.timelines', { diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index cf63df9ca0acb..ad17fa0652491 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -47,7 +47,8 @@ import type { Overview } from './overview'; import type { Rules } from './rules'; import type { Timelines } from './timelines'; import type { Management } from './management'; -import { LandingPages } from './landing_pages'; +import type { LandingPages } from './landing_pages'; +import type { CloudSecurityPosture } from './cloud_security_posture'; export interface SetupPlugins { home?: HomePublicPluginSetup; @@ -114,6 +115,7 @@ export interface SubPlugins { timelines: Timelines; management: Management; landingPages: LandingPages; + cloudSecurityPosture: CloudSecurityPosture; } // TODO: find a better way to defined these types @@ -130,4 +132,5 @@ export interface StartedSubPlugins { timelines: ReturnType<Timelines['start']>; management: ReturnType<Management['start']>; landingPages: ReturnType<LandingPages['start']>; + cloudSecurityPosture: ReturnType<CloudSecurityPosture['start']>; } diff --git a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx index 121ccae4553fc..0d0eee5848bb5 100644 --- a/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx +++ b/x-pack/plugins/security_solution/public/users/components/user_risk_score_table/index.tsx @@ -9,6 +9,7 @@ import React, { useMemo, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui'; +import styled from 'styled-components'; import { Columns, Criteria, @@ -37,6 +38,10 @@ import { UsersRiskScore, } from '../../../../common/search_strategy'; +const IconWrapper = styled.span` + margin-left: ${({ theme }) => theme.eui.euiSizeS}; +`; + export const rowItems: ItemsPerRow[] = [ { text: i18n.ROWS_5, @@ -154,9 +159,9 @@ const UserRiskScoreTableComponent: React.FC<UserRiskScoreTableProps> = ({ ); const headerTitle = ( - <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> - <EuiFlexItem grow={false}>{i18nUsers.NAVIGATION_RISK_TITLE}</EuiFlexItem> - <EuiFlexItem grow={false}> + <> + {i18nUsers.NAVIGATION_RISK_TITLE} + <IconWrapper> <EuiIconTip color="subdued" content={i18n.USER_RISK_TABLE_TOOLTIP} @@ -164,8 +169,8 @@ const UserRiskScoreTableComponent: React.FC<UserRiskScoreTableProps> = ({ size="l" type="iInCircle" /> - </EuiFlexItem> - </EuiFlexGroup> + </IconWrapper> + </> ); const getUserRiskScoreFilterQuerySelector = useMemo( diff --git a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx index 01185ccfc323f..9db18bde62188 100644 --- a/x-pack/plugins/security_solution/public/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/users/pages/details/index.tsx @@ -165,6 +165,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({ to: fromTo.to, }); }} + indexPatterns={selectedPatterns} /> )} </AnomalyTableProvider> diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts index 2d5790d09a8f4..e4c66d5ca0538 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.test.ts @@ -131,6 +131,7 @@ describe('Response actions', () => { }, ], keep_policies_up_to_date: false, + verification_status: 'unknown', }); licenseEmitter = new Subject(); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 628672b13006e..39f1890df7ca9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -142,6 +142,7 @@ describe('test endpoint routes', () => { }, ], keep_policies_up_to_date: false, + verification_status: 'unknown', }); endpointAppContextService.setup(createMockEndpointAppContextServiceSetupContract()); endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts index 8db750ba220af..911eb988b39e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.test.ts @@ -13,7 +13,7 @@ import { getBasicEmptySearchResponse, } from '../__mocks__/request_responses'; import { requestContextMock, serverMock } from '../__mocks__'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { addPrepackedRulesRoute, createPrepackagedRules } from './add_prepackaged_rules_route'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import { ExceptionListClient } from '@kbn/lists-plugin/server'; @@ -33,7 +33,7 @@ jest.mock('../../rules/utils', () => { jest.mock('../../rules/get_prepackaged_rules', () => { return { - getLatestPrepackagedRules: async (): Promise<AddPrepackagedRulesSchemaDecoded[]> => { + getLatestPrepackagedRules: async (): Promise<AddPrepackagedRulesSchema[]> => { return [ { author: ['Elastic'], @@ -58,7 +58,7 @@ jest.mock('../../rules/get_prepackaged_rules', () => { false_positives: [], max_signals: 100, threat: [], - throttle: null, + throttle: undefined, exceptions_list: [], version: 2, // set one higher than the mocks which is set to 1 to trigger updates }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 145558cd4a104..b46dcec9f5155 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -118,9 +118,8 @@ export const createPrepackagedRules = async ( }); const rulesToInstall = getRulesToInstall(latestPrepackagedRules, prepackagedRules); const rulesToUpdate = getRulesToUpdate(latestPrepackagedRules, prepackagedRules); - const signalsIndex = siemClient.getSignalsIndex(); - await Promise.all(installPrepackagedRules(rulesClient, rulesToInstall, signalsIndex)); + await Promise.all(installPrepackagedRules(rulesClient, rulesToInstall)); const timeline = await installPrepackagedTimelines( maxTimelineImportExportSize, frameworkRequest, @@ -134,7 +133,6 @@ export const createPrepackagedRules = async ( rulesClient, savedObjectsClient, rulesToUpdate, - signalsIndex, context.getRuleExecutionLog() ); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index d0028bbf7752b..a68a8febb603e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -11,10 +11,7 @@ import { createRuleValidateTypeDependents } from '../../../../../common/detectio import { createRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; -import { - DETECTION_ENGINE_RULES_BULK_CREATE, - NOTIFICATION_THROTTLE_NO_ACTIONS, -} from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_BULK_CREATE } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import { buildMlAuthz } from '../../../machine_learning/authz'; import { throwAuthzError } from '../../../machine_learning/validation'; @@ -24,8 +21,8 @@ import { transformValidateBulkError } from './validate'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; import { transformBulkError, createBulkErrorObject, buildSiemResponse } from '../utils'; -import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; +import { createRules } from '../../rules/create_rules'; /** * @deprecated since version 8.2.0. Use the detection_engine/rules/_bulk_action API instead @@ -54,7 +51,6 @@ export const createRulesBulkRoute = ( const rulesClient = ctx.alerting.getRulesClient(); const savedObjectsClient = ctx.core.savedObjects.client; - const siemClient = ctx.securitySolution.getAppClient(); const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, @@ -84,32 +80,28 @@ export const createRulesBulkRoute = ( }); } } - const internalRule = convertCreateAPIToInternalSchema(payloadRule, siemClient); + try { const validationErrors = createRuleValidateTypeDependents(payloadRule); if (validationErrors.length) { return createBulkErrorObject({ - ruleId: internalRule.params.ruleId, + ruleId: payloadRule.rule_id, statusCode: 400, message: validationErrors.join(), }); } - throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(payloadRule.type)); - const createdRule = await rulesClient.create({ - data: internalRule, + const createdRule = await createRules({ + rulesClient, + params: payloadRule, }); - // mutes if we are creating the rule with the explicit "no_actions" - if (payloadRule.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { - await rulesClient.muteAll({ id: createdRule.id }); - } - - return transformValidateBulkError(internalRule.params.ruleId, createdRule, null); + return transformValidateBulkError(createdRule.params.ruleId, createdRule, null); } catch (err) { return transformBulkError( - internalRule.params.ruleId, + payloadRule.rule_id, err as Error & { statusCode?: number } ); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts index b44b2fd0bd6a5..11e5f34fbe9a6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -7,10 +7,7 @@ import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { - DETECTION_ENGINE_RULES_URL, - NOTIFICATION_THROTTLE_NO_ACTIONS, -} from '../../../../../common/constants'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { buildMlAuthz } from '../../../machine_learning/authz'; @@ -21,7 +18,7 @@ import { buildSiemResponse } from '../utils'; import { createRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { newTransformValidate } from './validate'; import { createRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/create_rules_type_dependents'; -import { convertCreateAPIToInternalSchema } from '../../schemas/rule_converters'; +import { createRules } from '../../rules/create_rules'; export const createRulesRoute = ( router: SecuritySolutionPluginRouter, @@ -56,7 +53,6 @@ export const createRulesRoute = ( const rulesClient = ctx.alerting.getRulesClient(); const ruleExecutionLog = ctx.securitySolution.getRuleExecutionLog(); const savedObjectsClient = ctx.core.savedObjects.client; - const siemClient = ctx.securitySolution.getAppClient(); if (request.body.rule_id != null) { const rule = await readRules({ @@ -72,28 +68,21 @@ export const createRulesRoute = ( } } - const internalRule = convertCreateAPIToInternalSchema(request.body, siemClient); - const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, ml, request, savedObjectsClient, }); - throwAuthzError(await mlAuthz.validateRuleType(internalRule.params.type)); + throwAuthzError(await mlAuthz.validateRuleType(request.body.type)); // This will create the endpoint list if it does not exist yet await ctx.lists?.getExceptionListClient().createEndpointList(); - - const createdRule = await rulesClient.create({ - data: internalRule, + const createdRule = await createRules({ + rulesClient, + params: request.body, }); - // mutes if we are creating the rule with the explicit "no_actions" - if (request.body.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { - await rulesClient.muteAll({ id: createdRule.id }); - } - const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(createdRule.id); const [validated, errors] = newTransformValidate(createdRule, ruleExecutionSummary); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts index 00d10ef8be9fa..51c494b183b88 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -45,7 +45,7 @@ import { } from './utils/import_rules_utils'; import { getReferencedExceptionLists } from './utils/gather_referenced_exceptions'; import { importRuleExceptions } from './utils/import_rule_exceptions'; -import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request'; +import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; const CHUNK_PARSED_OBJECT_SIZE = 50; @@ -140,10 +140,10 @@ export const importRulesRoute = ( let parsedRules; let actionErrors: BulkError[] = []; const actualRules = rules.filter( - (rule): rule is ImportRulesSchemaDecoded => !(rule instanceof Error) + (rule): rule is ImportRulesSchema => !(rule instanceof Error) ); - if (actualRules.some((rule) => rule.actions.length > 0)) { + if (actualRules.some((rule) => rule.actions && rule.actions.length > 0)) { const [nonExistentActionErrors, uniqueParsedObjects] = await getInvalidConnectors( migratedParsedObjectsWithoutDuplicateErrors, actionsClient diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts index fa75be49a61c5..da2808e572cf3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.test.ts @@ -21,7 +21,7 @@ import { import { serverMock, requestContextMock, requestMock } from '../__mocks__'; import { patchRulesBulkRoute } from './patch_rules_bulk_route'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getMlRuleParams, getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { legacyMigrate } from '../../rules/utils'; @@ -80,6 +80,12 @@ describe('patch_rules_bulk', () => { }); test('allows ML Params to be patched', async () => { + clients.rulesClient.get.mockResolvedValueOnce(getRuleMock(getMlRuleParams())); + clients.rulesClient.find.mockResolvedValueOnce({ + ...getFindResultWithSingleHit(), + data: [getRuleMock(getMlRuleParams())], + }); + (legacyMigrate as jest.Mock).mockResolvedValueOnce(getRuleMock(getMlRuleParams())); const request = requestMock.create({ method: 'patch', path: `${DETECTION_ENGINE_RULES_URL}/bulk_update`, @@ -201,7 +207,7 @@ describe('patch_rules_bulk', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "unknown_type" supplied to "type"' + 'Invalid value "unknown_type" supplied to "type",Invalid value "kuery" supplied to "language"' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 8cb03c7790733..bb4ba585dccaf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -7,12 +7,8 @@ import { validate } from '@kbn/securitysolution-io-ts-utils'; import { Logger } from '@kbn/core/server'; -import { RuleAlertAction } from '../../../../../common/detection_engine/types'; -import { - patchRulesBulkSchema, - PatchRulesBulkSchemaDecoded, -} from '../../../../../common/detection_engine/schemas/request/patch_rules_bulk_schema'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; +import { patchRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_bulk_schema'; +import { buildRouteValidationNonExact } from '../../../../utils/build_validation/route_validation'; import { rulesBulkSchema } from '../../../../../common/detection_engine/schemas/response/rules_bulk_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_BULK_UPDATE } from '../../../../../common/constants'; @@ -24,7 +20,6 @@ import { getIdBulkError } from './utils'; import { transformValidateBulkError } from './validate'; import { patchRules } from '../../rules/patch_rules'; import { readRules } from '../../rules/read_rules'; -import { PartialFilter } from '../../types'; import { legacyMigrate } from '../../rules/utils'; import { getDeprecatedBulkEndpointHeader, logDeprecatedBulkEndpoint } from './utils/deprecation'; @@ -40,9 +35,7 @@ export const patchRulesBulkRoute = ( { path: DETECTION_ENGINE_RULES_BULK_UPDATE, validate: { - body: buildRouteValidation<typeof patchRulesBulkSchema, PatchRulesBulkSchemaDecoded>( - patchRulesBulkSchema - ), + body: buildRouteValidationNonExact<typeof patchRulesBulkSchema>(patchRulesBulkSchema), }, options: { tags: ['access:securitySolution'], @@ -67,75 +60,18 @@ export const patchRulesBulkRoute = ( }); const rules = await Promise.all( request.body.map(async (payloadRule) => { - const { - actions: actionsRest, - author, - building_block_type: buildingBlockType, - description, - enabled, - timestamp_field: timestampField, - event_category_override: eventCategoryOverride, - tiebreaker_field: tiebreakerField, - false_positives: falsePositives, - from, - query, - language, - license, - output_index: outputIndex, - saved_id: savedId, - timeline_id: timelineId, - timeline_title: timelineTitle, - meta, - filters: filtersRest, - rule_id: ruleId, - id, - index, - data_view_id: dataViewId, - interval, - max_signals: maxSignals, - risk_score: riskScore, - risk_score_mapping: riskScoreMapping, - rule_name_override: ruleNameOverride, - name, - severity, - severity_mapping: severityMapping, - tags, - to, - type, - threat, - threshold, - threat_filters: threatFilters, - threat_index: threatIndex, - threat_indicator_path: threatIndicatorPath, - threat_query: threatQuery, - threat_mapping: threatMapping, - threat_language: threatLanguage, - concurrent_searches: concurrentSearches, - items_per_search: itemsPerSearch, - timestamp_override: timestampOverride, - throttle, - references, - note, - version, - anomaly_threshold: anomalyThreshold, - machine_learning_job_id: machineLearningJobId, - exceptions_list: exceptionsList, - } = payloadRule; - const idOrRuleIdOrUnknown = id ?? ruleId ?? '(unknown id)'; - // TODO: Fix these either with an is conversion or by better typing them within io-ts - const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[]; - const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; + const idOrRuleIdOrUnknown = payloadRule.id ?? payloadRule.rule_id ?? '(unknown id)'; try { - if (type) { + if (payloadRule.type) { // reject an unauthorized "promotion" to ML - throwAuthzError(await mlAuthz.validateRuleType(type)); + throwAuthzError(await mlAuthz.validateRuleType(payloadRule.type)); } const existingRule = await readRules({ rulesClient, - ruleId, - id, + ruleId: payloadRule.rule_id, + id: payloadRule.id, }); if (existingRule?.params.type) { // reject an unauthorized modification of an ML rule @@ -151,62 +87,13 @@ export const patchRulesBulkRoute = ( const rule = await patchRules({ rule: migratedRule, rulesClient, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - index, - dataViewId, - interval, - maxSignals, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - severity, - severityMapping, - tags, - to, - type, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - throttle, - concurrentSearches, - itemsPerSearch, - timestampOverride, - references, - note, - version, - anomalyThreshold, - machineLearningJobId, - actions, - exceptionsList, + params: payloadRule, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(rule.id); return transformValidateBulkError(rule.id, rule, ruleExecutionSummary); } else { - return getIdBulkError({ id, ruleId }); + return getIdBulkError({ id: payloadRule.id, ruleId: payloadRule.rule_id }); } } catch (err) { return transformBulkError(idOrRuleIdOrUnknown, err); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts index 87c2e79922457..40a903ba1b61b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.test.ts @@ -20,7 +20,7 @@ import { import { requestContextMock, serverMock, requestMock } from '../__mocks__'; import { patchRulesRoute } from './patch_rules_route'; import { getPatchRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema.mock'; -import { getQueryRuleParams } from '../../schemas/rule_schemas.mock'; +import { getMlRuleParams, getQueryRuleParams } from '../../schemas/rule_schemas.mock'; import { legacyMigrate } from '../../rules/utils'; jest.mock('../../../machine_learning/authz', () => mockMlAuthzFactory.create()); @@ -108,6 +108,12 @@ describe('patch_rules', () => { }); test('allows ML Params to be patched', async () => { + clients.rulesClient.get.mockResolvedValueOnce(getRuleMock(getMlRuleParams())); + clients.rulesClient.find.mockResolvedValueOnce({ + ...getFindResultWithSingleHit(), + data: [getRuleMock(getMlRuleParams())], + }); + (legacyMigrate as jest.Mock).mockResolvedValueOnce(getRuleMock(getMlRuleParams())); const request = requestMock.create({ method: 'patch', path: DETECTION_ENGINE_RULES_URL, @@ -208,7 +214,7 @@ describe('patch_rules', () => { const result = server.validate(request); expect(result.badRequest).toHaveBeenCalledWith( - 'Invalid value "unknown_type" supplied to "type"' + 'Invalid value "unknown_type" supplied to "type",Invalid value "kuery" supplied to "language"' ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts index d704f5678f53d..eb6132ae3e1e4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -6,13 +6,9 @@ */ import { transformError } from '@kbn/securitysolution-es-utils'; -import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { patchRuleValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/patch_rules_type_dependents'; -import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; -import { - PatchRulesSchemaDecoded, - patchRulesSchema, -} from '../../../../../common/detection_engine/schemas/request/patch_rules_schema'; +import { buildRouteValidationNonExact } from '../../../../utils/build_validation/route_validation'; +import { patchRulesSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { SetupPlugins } from '../../../../plugin'; @@ -25,16 +21,16 @@ import { getIdError } from './utils'; import { transformValidate } from './validate'; import { readRules } from '../../rules/read_rules'; import { legacyMigrate } from '../../rules/utils'; -import { PartialFilter } from '../../types'; export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupPlugins['ml']) => { router.patch( { path: DETECTION_ENGINE_RULES_URL, validate: { - body: buildRouteValidation<typeof patchRulesSchema, PatchRulesSchemaDecoded>( - patchRulesSchema - ), + // Use non-exact validation because everything is optional in patch - since everything is optional, + // io-ts can't find the right schema from the type specific union and the exact check breaks. + // We do type specific validation after fetching the existing rule so we know the rule type. + body: buildRouteValidationNonExact<typeof patchRulesSchema>(patchRulesSchema), }, options: { tags: ['access:securitySolution'], @@ -46,65 +42,8 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } - const { - actions: actionsRest, - author, - building_block_type: buildingBlockType, - description, - enabled, - timestamp_field: timestampField, - event_category_override: eventCategoryOverride, - tiebreaker_field: tiebreakerField, - false_positives: falsePositives, - from, - query, - language, - license, - output_index: outputIndex, - saved_id: savedId, - timeline_id: timelineId, - timeline_title: timelineTitle, - meta, - filters: filtersRest, - rule_id: ruleId, - id, - index, - data_view_id: dataViewId, - interval, - max_signals: maxSignals, - risk_score: riskScore, - risk_score_mapping: riskScoreMapping, - rule_name_override: ruleNameOverride, - name, - severity, - severity_mapping: severityMapping, - tags, - to, - type, - threat, - threshold, - threat_filters: threatFilters, - threat_index: threatIndex, - threat_indicator_path: threatIndicatorPath, - threat_query: threatQuery, - threat_mapping: threatMapping, - threat_language: threatLanguage, - concurrent_searches: concurrentSearches, - items_per_search: itemsPerSearch, - timestamp_override: timestampOverride, - throttle, - references, - note, - version, - anomaly_threshold: anomalyThreshold, - machine_learning_job_id: machineLearningJobId, - exceptions_list: exceptionsList, - } = request.body; try { - // TODO: Fix these either with an is conversion or by better typing them within io-ts - const actions: RuleAlertAction[] = actionsRest as RuleAlertAction[]; - const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; - + const params = request.body; const rulesClient = (await context.alerting).getRulesClient(); const ruleExecutionLog = (await context.securitySolution).getRuleExecutionLog(); const savedObjectsClient = (await context.core).savedObjects.client; @@ -115,15 +54,15 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP request, savedObjectsClient, }); - if (type) { + if (params.type) { // reject an unauthorized "promotion" to ML - throwAuthzError(await mlAuthz.validateRuleType(type)); + throwAuthzError(await mlAuthz.validateRuleType(params.type)); } const existingRule = await readRules({ rulesClient, - ruleId, - id, + ruleId: params.rule_id, + id: params.id, }); if (existingRule?.params.type) { // reject an unauthorized modification of an ML rule @@ -138,57 +77,8 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP const rule = await patchRules({ rulesClient, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, rule: migratedRule, - index, - dataViewId, - interval, - maxSignals, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - severity, - severityMapping, - tags, - to, - type, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - throttle, - concurrentSearches, - itemsPerSearch, - timestampOverride, - references, - note, - version, - anomalyThreshold, - machineLearningJobId, - actions, - exceptionsList, + params, }); if (rule != null && rule.enabled != null && rule.name != null) { const ruleExecutionSummary = await ruleExecutionLog.getExecutionSummary(rule.id); @@ -200,7 +90,7 @@ export const patchRulesRoute = (router: SecuritySolutionPluginRouter, ml: SetupP return response.ok({ body: validated ?? {} }); } } else { - const error = getIdError({ id, ruleId }); + const error = getIdError({ id: params.id, ruleId: params.rule_id }); return siemResponse.error({ body: error.message, statusCode: error.statusCode, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts index de60e82e336ef..8fe7f9cc3a5fa 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/preview_rules_route.ts @@ -105,7 +105,7 @@ export const previewRulesRoute = async ( }); } - const internalRule = convertCreateAPIToInternalSchema(request.body, siemClient); + const internalRule = convertCreateAPIToInternalSchema(request.body); const previewRuleParams = internalRule.params; const mlAuthz = buildMlAuthz({ 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 0ba03ea999548..a9f27331dafb9 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 @@ -52,7 +52,6 @@ export const updateRulesBulkRoute = ( const rulesClient = ctx.alerting.getRulesClient(); const ruleExecutionLog = ctx.securitySolution.getRuleExecutionLog(); const savedObjectsClient = ctx.core.savedObjects.client; - const siemClient = ctx.securitySolution.getAppClient(); const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, @@ -90,7 +89,6 @@ export const updateRulesBulkRoute = ( const rule = await updateRules({ rulesClient, - defaultOutputIndex: siemClient.getSignalsIndex(), existingRule: migratedRule, ruleUpdate: payloadRule, }); 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 ec16488944b55..2234de0ff73af 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 @@ -44,7 +44,6 @@ export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: Setup const rulesClient = ctx.alerting.getRulesClient(); const savedObjectsClient = ctx.core.savedObjects.client; - const siemClient = ctx.securitySolution.getAppClient(); const mlAuthz = buildMlAuthz({ license: ctx.licensing.license, @@ -66,7 +65,6 @@ export const updateRulesRoute = (router: SecuritySolutionPluginRouter, ml: Setup rule: existingRule, }); const rule = await updateRules({ - defaultOutputIndex: siemClient.getSignalsIndex(), rulesClient, existingRule: migratedRule, ruleUpdate: request.body, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts index 2fb1dccc64f80..6f3891a2c1dfb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.test.ts @@ -28,7 +28,7 @@ import { getOutputRuleAlertForRest } from '../__mocks__/utils'; import { PartialRule } from '@kbn/alerting-plugin/server'; import { createRulesAndExceptionsStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; import { RuleAlertType } from '../../rules/types'; -import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request'; import { @@ -46,7 +46,7 @@ import { LegacyRuleAlertAction } from '../../rule_actions/legacy_types'; import { RuleExceptionsPromiseFromStreams } from './utils/import_rules_utils'; import { partition } from 'lodash/fp'; -type PromiseFromStreams = ImportRulesSchemaDecoded | Error; +type PromiseFromStreams = ImportRulesSchema | Error; const createMockImportRule = async (rule: ReturnType<typeof getCreateRulesSchemaMock>) => { const ndJsonStream = new Readable({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts index 850d971b39c59..15c7c551f699f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils.ts @@ -15,7 +15,7 @@ import { PartialRule, FindResult } from '@kbn/alerting-plugin/server'; import { ActionsClient, FindActionResult } from '@kbn/actions-plugin/server'; import { RuleExecutionSummary } from '../../../../../common/detection_engine/schemas/common'; import { RulesSchema } from '../../../../../common/detection_engine/schemas/response/rules_schema'; -import { ImportRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { CreateRulesBulkSchema } from '../../../../../common/detection_engine/schemas/request/create_rules_bulk_schema'; import { RuleAlertType, isAlertType } from '../../rules/types'; import { createBulkErrorObject, BulkError, OutputError } from '../utils'; @@ -25,7 +25,7 @@ import { RuleParams } from '../../schemas/rule_schemas'; import { LegacyRulesActionsSavedObject } from '../../rule_actions/legacy_get_rule_actions_saved_object'; import { RuleExecutionSummariesByRuleId } from '../../rule_execution_log'; -type PromiseFromStreams = ImportRulesSchemaDecoded | Error; +type PromiseFromStreams = ImportRulesSchema | Error; const MAX_CONCURRENT_SEARCHES = 10; export const getIdError = ({ @@ -245,7 +245,7 @@ export const migrateLegacyActionsIds = async ( rules: PromiseFromStreams[], savedObjectsClient: SavedObjectsClientContract ): Promise<PromiseFromStreams[]> => { - const isImportRule = (r: unknown): r is ImportRulesSchemaDecoded => !(r instanceof Error); + const isImportRule = (r: unknown): r is ImportRulesSchema => !(r instanceof Error); const toReturn = await pMap( rules, @@ -254,7 +254,7 @@ export const migrateLegacyActionsIds = async ( // can we swap the pre 8.0 action connector(s) id with the new, // post-8.0 action id (swap the originId for the new _id?) const newActions: Array<Action | Error> = await pMap( - rule.actions, + rule.actions ?? [], (action: Action) => swapActionIds(action, savedObjectsClient), { concurrency: MAX_CONCURRENT_SEARCHES } ); @@ -332,13 +332,15 @@ export const getInvalidConnectors = async ( acc.rulesAcc.set(uuid.v4(), parsedRule); } else { const { rule_id: ruleId, actions } = parsedRule; - const missingActionIds = actions.flatMap((action) => { - if (!actionIds.has(action.id)) { - return [action.id]; - } else { - return []; - } - }); + const missingActionIds = actions + ? actions.flatMap((action) => { + if (!actionIds.has(action.id)) { + return [action.id]; + } else { + return []; + } + }) + : []; if (missingActionIds.length === 0) { acc.rulesAcc.set(ruleId, parsedRule); } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts index c46efeabd1dac..d1fb4881ae9a8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getImportRulesSchemaDecodedMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; @@ -13,7 +13,7 @@ describe('checkRuleExceptionReferences', () => { it('returns empty array if rule has no exception list references', () => { const result = checkRuleExceptionReferences({ existingLists: {}, - rule: { ...getImportRulesSchemaDecodedMock(), exceptions_list: [] }, + rule: { ...getImportRulesSchemaMock(), exceptions_list: [] }, }); expect(result).toEqual([[], []]); @@ -30,7 +30,7 @@ describe('checkRuleExceptionReferences', () => { }, }, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], @@ -54,7 +54,7 @@ describe('checkRuleExceptionReferences', () => { const result = checkRuleExceptionReferences({ existingLists: {}, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], @@ -87,7 +87,7 @@ describe('checkRuleExceptionReferences', () => { }, }, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], @@ -119,7 +119,7 @@ describe('checkRuleExceptionReferences', () => { }, }, rule: { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts index 11600443a9f6b..4ad7dc51821cd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/check_rule_exception_references.ts @@ -6,7 +6,7 @@ */ import { ListArray, ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { ImportRulesSchemaDecoded } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { BulkError, createBulkErrorObject } from '../../utils'; /** @@ -24,12 +24,13 @@ export const checkRuleExceptionReferences = ({ rule, existingLists, }: { - rule: ImportRulesSchemaDecoded; + rule: ImportRulesSchema; existingLists: Record<string, ExceptionListSchema>; }): [BulkError[], ListArray] => { let ruleExceptions: ListArray = []; let errors: BulkError[] = []; - const { exceptions_list: exceptionLists, rule_id: ruleId } = rule; + const { rule_id: ruleId } = rule; + const exceptionLists = rule.exceptions_list ?? []; if (!exceptionLists.length) { return [[], []]; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts index b67db94bcef00..5206e85e8fdb2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.test.ts @@ -11,7 +11,7 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { findExceptionList } from '@kbn/lists-plugin/server/services/exception_lists/find_exception_list'; import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock'; import { getReferencedExceptionLists } from './gather_referenced_exceptions'; -import { getImportRulesSchemaDecodedMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; jest.mock('@kbn/lists-plugin/server/services/exception_lists/find_exception_list'); @@ -51,7 +51,7 @@ describe('getReferencedExceptionLists', () => { const result = await getReferencedExceptionLists({ rules: [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), exceptions_list: [ { id: '123', list_id: 'my-list', namespace_type: 'single', type: 'detection' }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts index 4033e572553f5..5158b361bdd64 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/gather_referenced_exceptions.ts @@ -11,7 +11,7 @@ import { getAllListTypes, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '@kbn/lists-plugin/server/services/exception_lists/utils/import/find_all_exception_list_types'; -import { ImportRulesSchemaDecoded } from '../../../../../../common/detection_engine/schemas/request'; +import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; /** * Helper that takes rules, goes through their referenced exception lists and @@ -24,7 +24,7 @@ export const getReferencedExceptionLists = async ({ rules, savedObjectsClient, }: { - rules: Array<ImportRulesSchemaDecoded | Error>; + rules: Array<ImportRulesSchema | Error>; savedObjectsClient: SavedObjectsClientContract; }): Promise<Record<string, ExceptionListSchema>> => { const [lists] = rules.reduce<ListArray[]>((acc, rule) => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts index 1a9d10d2a3d29..c98bac15d457c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.test.ts @@ -13,7 +13,7 @@ import { getFindResultWithSingleHit, } from '../../__mocks__/request_responses'; import { getQueryRuleParams } from '../../../schemas/rule_schemas.mock'; -import { getImportRulesSchemaDecodedMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; +import { getImportRulesSchemaMock } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema.mock'; import { createRules } from '../../../rules/create_rules'; import { patchRules } from '../../../rules/patch_rules'; @@ -81,7 +81,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -108,7 +108,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -140,7 +140,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -167,7 +167,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -202,7 +202,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], @@ -236,7 +236,7 @@ describe('importRules', () => { ruleChunks: [ [ { - ...getImportRulesSchemaDecodedMock(), + ...getImportRulesSchemaMock(), rule_id: 'rule-1', }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts index dd98137b3b2e4..3a69febefbeea 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/rules/utils/import_rules_utils.ts @@ -15,18 +15,16 @@ import { import { RulesClient } from '@kbn/alerting-plugin/server'; import { ExceptionListClient } from '@kbn/lists-plugin/server'; import { legacyMigrate } from '../../../rules/utils'; -import { PartialFilter } from '../../../types'; import { createBulkErrorObject, ImportRuleResponse } from '../../utils'; -import { isMlRule } from '../../../../../../common/machine_learning/helpers'; import { createRules } from '../../../rules/create_rules'; import { readRules } from '../../../rules/read_rules'; import { patchRules } from '../../../rules/patch_rules'; -import { ImportRulesSchemaDecoded } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../../../common/detection_engine/schemas/request/import_rules_schema'; import { MlAuthz } from '../../../../machine_learning/authz'; import { throwAuthzError } from '../../../../machine_learning/validation'; import { checkRuleExceptionReferences } from './check_rule_exception_references'; -export type PromiseFromStreams = ImportRulesSchemaDecoded | Error; +export type PromiseFromStreams = ImportRulesSchema | Error; export interface RuleExceptionsPromiseFromStreams { rules: PromiseFromStreams[]; exceptions: Array<ImportExceptionsListSchema | ImportExceptionListItemSchema>; @@ -95,63 +93,6 @@ export const importRules = async ({ return null; } - const { - anomaly_threshold: anomalyThreshold, - author, - building_block_type: buildingBlockType, - description, - enabled, - timestamp_field: timestampField, - event_category_override: eventCategoryOverride, - tiebreaker_field: tiebreakerField, - false_positives: falsePositives, - from, - immutable, - query: queryOrUndefined, - language: languageOrUndefined, - license, - machine_learning_job_id: machineLearningJobId, - output_index: outputIndex, - saved_id: savedId, - meta, - filters: filtersRest, - rule_id: ruleId, - index, - data_view_id: dataViewId, - interval, - max_signals: maxSignals, - related_integrations: relatedIntegrations, - required_fields: requiredFields, - risk_score: riskScore, - risk_score_mapping: riskScoreMapping, - rule_name_override: ruleNameOverride, - name, - setup, - severity, - severity_mapping: severityMapping, - tags, - threat, - threat_filters: threatFilters, - threat_index: threatIndex, - threat_query: threatQuery, - threat_mapping: threatMapping, - threat_language: threatLanguage, - threat_indicator_path: threatIndicatorPath, - concurrent_searches: concurrentSearches, - items_per_search: itemsPerSearch, - threshold, - timestamp_override: timestampOverride, - to, - type, - references, - note, - timeline_id: timelineId, - timeline_title: timelineTitle, - throttle, - version, - actions, - } = parsedRule; - try { const [exceptionErrors, exceptions] = checkRuleExceptionReferences({ rule: parsedRule, @@ -160,78 +101,23 @@ export const importRules = async ({ importRuleResponse = [...importRuleResponse, ...exceptionErrors]; - const query = !isMlRule(type) && queryOrUndefined == null ? '' : queryOrUndefined; - const language = - !isMlRule(type) && languageOrUndefined == null ? 'kuery' : languageOrUndefined; // TODO: Fix these either with an is conversion or by better typing them within io-ts - const filters: PartialFilter[] | undefined = filtersRest as PartialFilter[]; - throwAuthzError(await mlAuthz.validateRuleType(type)); + throwAuthzError(await mlAuthz.validateRuleType(parsedRule.type)); const rule = await readRules({ rulesClient, - ruleId, + ruleId: parsedRule.rule_id, id: undefined, }); if (rule == null) { await createRules({ rulesClient, - anomalyThreshold, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - immutable, - query, - language, - license, - machineLearningJobId, - outputIndex: '', - savedId, - timelineId, - timelineTitle, - meta, - filters, - ruleId, - index, - dataViewId, - interval, - maxSignals, - name, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - setup, - severity, - severityMapping, - tags, - throttle, - to, - type, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - timestampOverride, - references, - note, - version, - exceptionsList: [...exceptions], - actions, + params: { + ...parsedRule, + exceptions_list: [...exceptions], + }, }); resolve({ - rule_id: ruleId, + rule_id: parsedRule.rule_id, status_code: 200, }); } else if (rule != null && overwriteRules) { @@ -242,78 +128,29 @@ export const importRules = async ({ }); await patchRules({ rulesClient, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, rule: migratedRule, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - setup, - severity, - severityMapping, - tags, - timestampOverride, - throttle, - to, - type, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - references, - note, - version, - exceptionsList: [...exceptions], - anomalyThreshold, - machineLearningJobId, - actions, + params: { + ...parsedRule, + exceptions_list: [...exceptions], + }, }); resolve({ - rule_id: ruleId, + rule_id: parsedRule.rule_id, status_code: 200, }); } else if (rule != null) { resolve( createBulkErrorObject({ - ruleId, + ruleId: parsedRule.rule_id, statusCode: 409, - message: `rule_id: "${ruleId}" already exists`, + message: `rule_id: "${parsedRule.rule_id}" already exists`, }) ); } } catch (err) { resolve( createBulkErrorObject({ - ruleId, + ruleId: parsedRule.rule_id, statusCode: err.statusCode ?? 400, message: err.message, }) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts index 662e20368ff99..9246beea7bb7d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.ts @@ -97,7 +97,7 @@ export const isImportRegular = ( }; export const transformBulkError = ( - ruleId: string, + ruleId: string | undefined, err: Error & { statusCode?: number } ): BulkError => { if (err instanceof CustomHttpRequestError) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts deleted file mode 100644 index 00a8b381b05d7..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.mock.ts +++ /dev/null @@ -1,196 +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 { CreateRulesOptions } from './types'; -import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; - -export const getCreateRulesOptionsMock = (): CreateRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: undefined, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - query: 'user.name: root or user.name: admin', - language: 'kuery', - license: 'Elastic License', - savedId: 'savedId-123', - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - meta: {}, - machineLearningJobId: undefined, - filters: [], - ruleId: 'rule-1', - immutable: false, - index: ['index-123'], - dataViewId: undefined, - interval: '5m', - maxSignals: 100, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Query with a rule id', - setup: undefined, - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threatFilters: undefined, - threatMapping: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - threatQuery: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threshold: undefined, - timestampOverride: undefined, - throttle: null, - to: 'now', - type: 'query', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], -}); - -export const getCreateMlRulesOptionsMock = (): CreateRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: 55, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - query: undefined, - language: undefined, - license: 'Elastic License', - savedId: 'savedId-123', - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - meta: {}, - machineLearningJobId: 'new_job_id', - filters: [], - ruleId: 'rule-1', - immutable: false, - index: ['index-123'], - dataViewId: undefined, - interval: '5m', - maxSignals: 100, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Machine Learning Job', - setup: undefined, - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatMapping: undefined, - threatQuery: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - threshold: undefined, - timestampOverride: undefined, - throttle: null, - to: 'now', - type: 'machine_learning', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], -}); - -export const getCreateThreatMatchRulesOptionsMock = (): CreateRulesOptions => ({ - actions: [], - anomalyThreshold: undefined, - author: ['Elastic'], - buildingBlockType: undefined, - concurrentSearches: undefined, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - exceptionsList: [], - falsePositives: ['false positive 1', 'false positive 2'], - filters: [], - from: 'now-1m', - immutable: false, - index: ['*'], - dataViewId: undefined, - interval: '5m', - itemsPerSearch: undefined, - language: 'kuery', - license: 'Elastic License', - machineLearningJobId: undefined, - maxSignals: 100, - meta: {}, - name: 'Query with a rule id', - note: '# sample markdown', - outputIndex: 'output-1', - query: 'user.name: root or user.name: admin', - references: ['http://www.example.com'], - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: 80, - riskScoreMapping: [], - ruleId: 'rule-1', - ruleNameOverride: undefined, - rulesClient: rulesClientMock.create(), - savedId: 'savedId-123', - setup: undefined, - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threatFilters: undefined, - threatIndex: ['filebeat-*'], - threatIndicatorPath: 'threat.indicator', - threatLanguage: 'kuery', - threatMapping: [ - { - entries: [ - { - field: 'file.hash.md5', - type: 'mapping', - value: 'threat.indicator.file.hash.md5', - }, - ], - }, - ], - threatQuery: '*:*', - threshold: undefined, - throttle: null, - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - timestampOverride: undefined, - to: 'now', - type: 'threat_match', - version: 1, -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts index ab683effd4c14..fb62434563183 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.test.ts @@ -5,23 +5,24 @@ * 2.0. */ +import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; import { createRules } from './create_rules'; -import { - getCreateMlRulesOptionsMock, - getCreateThreatMatchRulesOptionsMock, -} from './create_rules.mock'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../common/constants'; +import { + getCreateMachineLearningRulesSchemaMock, + getCreateThreatMatchRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; describe('createRules', () => { it('calls the rulesClient with legacy ML params', async () => { - const ruleOptions = getCreateMlRulesOptionsMock(); - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + await createRules({ rulesClient, params: getCreateMachineLearningRulesSchemaMock() }); + expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, - machineLearningJobId: ['new_job_id'], + anomalyThreshold: 58, + machineLearningJobId: ['typical-ml-job-id'], }), }), }) @@ -29,16 +30,19 @@ describe('createRules', () => { }); it('calls the rulesClient with ML params', async () => { - const ruleOptions = { - ...getCreateMlRulesOptionsMock(), - machineLearningJobId: ['new_job_1', 'new_job_2'], - }; - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + await createRules({ + rulesClient, + params: { + ...getCreateMachineLearningRulesSchemaMock(), + machine_learning_job_id: ['new_job_1', 'new_job_2'], + }, + }); + expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, + anomalyThreshold: 58, machineLearningJobId: ['new_job_1', 'new_job_2'], }), }), @@ -47,10 +51,11 @@ describe('createRules', () => { }); it('populates a threatIndicatorPath value for threat_match rule if empty', async () => { - const ruleOptions = getCreateThreatMatchRulesOptionsMock(); - delete ruleOptions.threatIndicatorPath; - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + const params = getCreateThreatMatchRulesSchemaMock(); + delete params.threat_indicator_path; + await createRules({ rulesClient, params }); + expect(rulesClient.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ @@ -62,10 +67,9 @@ describe('createRules', () => { }); it('does not populate a threatIndicatorPath value for other rules if empty', async () => { - const ruleOptions = getCreateMlRulesOptionsMock(); - delete ruleOptions.threatIndicatorPath; - await createRules(ruleOptions); - expect(ruleOptions.rulesClient.create).not.toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + await createRules({ rulesClient, params: getCreateMachineLearningRulesSchemaMock() }); + expect(rulesClient.create).not.toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts index 9e32ab79f2d07..45cba7c0032be 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules.ts @@ -5,161 +5,29 @@ * 2.0. */ -import { ruleTypeMappings } from '@kbn/securitysolution-rules'; - -import { RuleTypeParams, SanitizedRule } from '@kbn/alerting-plugin/common'; -import { - normalizeMachineLearningJobIds, - normalizeThresholdObject, -} from '../../../../common/detection_engine/utils'; -import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { - DEFAULT_INDICATOR_SOURCE_PATH, - NOTIFICATION_THROTTLE_NO_ACTIONS, - SERVER_APP_ID, -} from '../../../../common/constants'; +import { SanitizedRule } from '@kbn/alerting-plugin/common'; +import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../common/constants'; import { CreateRulesOptions } from './types'; -import { PartialFilter } from '../types'; -import { transformToAlertThrottle, transformToNotifyWhen } from './utils'; +import { convertCreateAPIToInternalSchema } from '../schemas/rule_converters'; +import { RuleParams } from '../schemas/rule_schemas'; export const createRules = async ({ rulesClient, - anomalyThreshold, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - savedId, - timelineId, - timelineTitle, - meta, - machineLearningJobId, - filters, - ruleId, - immutable, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - outputIndex, - name, - setup, - severity, - severityMapping, - tags, - threat, - threatFilters, - threatIndex, - threatIndicatorPath, - threatLanguage, - concurrentSearches, - itemsPerSearch, - threatQuery, - threatMapping, - threshold, - timestampOverride, - throttle, - to, - type, - references, - namespace, - note, - version, - exceptionsList, - actions, + params, id, -}: CreateRulesOptions): Promise<SanitizedRule<RuleTypeParams>> => { - const rule = await rulesClient.create<RuleTypeParams>({ + immutable = false, + defaultEnabled = true, +}: CreateRulesOptions): Promise<SanitizedRule<RuleParams>> => { + const internalRule = convertCreateAPIToInternalSchema(params, immutable, defaultEnabled); + const rule = await rulesClient.create<RuleParams>({ options: { id, }, - data: { - name, - tags, - alertTypeId: ruleTypeMappings[type], - consumer: SERVER_APP_ID, - params: { - anomalyThreshold, - author, - buildingBlockType, - description, - ruleId, - index, - dataViewId, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - immutable, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - machineLearningJobId: machineLearningJobId - ? normalizeMachineLearningJobIds(machineLearningJobId) - : undefined, - filters, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - setup, - severity, - severityMapping, - threat, - threshold: threshold ? normalizeThresholdObject(threshold) : undefined, - /** - * TODO: Fix typing inconsistancy between `RuleTypeParams` and `CreateRulesOptions` - */ - threatFilters: threatFilters as PartialFilter[] | undefined, - threatIndex, - threatIndicatorPath: - threatIndicatorPath ?? - (type === 'threat_match' ? DEFAULT_INDICATOR_SOURCE_PATH : undefined), - threatQuery, - concurrentSearches, - itemsPerSearch, - threatMapping, - threatLanguage, - timestampOverride, - to, - type, - references, - namespace, - note, - version, - exceptionsList, - }, - schedule: { interval }, - enabled, - actions: actions.map(transformRuleToAlertAction), - throttle: transformToAlertThrottle(throttle), - notifyWhen: transformToNotifyWhen(throttle), - }, + data: internalRule, }); // Mute the rule if it is first created with the explicit no actions - if (throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { + if (params.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS) { await rulesClient.muteAll({ id: rule.id }); } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts index 44c2d82fb00e1..50c4b8d378598 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -9,14 +9,14 @@ import { Readable } from 'stream'; import { createPromiseFromStreams } from '@kbn/utils'; import { createRulesAndExceptionsStreamFromNdJson } from './create_rules_stream_from_ndjson'; import { BadRequestError } from '@kbn/securitysolution-es-utils'; -import { ImportRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; import { getOutputDetailsSample, getSampleDetailsAsNdjson, } from '../../../../common/detection_engine/schemas/response/export_rules_details_schema.mock'; import { RuleExceptionsPromiseFromStreams } from '../routes/rules/utils/import_rules_utils'; -export const getOutputSample = (): Partial<ImportRulesSchemaDecoded> => ({ +export const getOutputSample = (): Partial<ImportRulesSchema> => ({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -30,7 +30,7 @@ export const getOutputSample = (): Partial<ImportRulesSchemaDecoded> => ({ type: 'query', }); -export const getSampleAsNdjson = (sample: Partial<ImportRulesSchemaDecoded>): string => { +export const getSampleAsNdjson = (sample: Partial<ImportRulesSchema>): string => { return `${JSON.stringify(sample)}\n`; }; @@ -53,58 +53,32 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); expect(result).toEqual([ { - author: [], - actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - exceptions_list: [], - max_signals: 100, - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, { - author: [], - actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - exceptions_list: [], - max_signals: 100, - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, ]); }); @@ -148,58 +122,32 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); expect(result).toEqual([ { - author: [], - actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - tags: [], - exceptions_list: [], - threat: [], - throttle: null, - references: [], - version: 1, }, { - author: [], - actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, ]); }); @@ -223,58 +171,32 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); expect(result).toEqual([ { - author: [], - actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, { - author: [], - actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }, ]); }); @@ -297,59 +219,33 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); const resultOrError = result as Error[]; expect(resultOrError[0]).toEqual({ - author: [], - actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); expect(resultOrError[1].message).toEqual('Unexpected token , in JSON at position 1'); expect(resultOrError[2]).toEqual({ - author: [], - actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); }); @@ -371,61 +267,35 @@ describe('create_rules_stream_from_ndjson', () => { >([ndJsonStream, ...rulesObjectsStream]); const resultOrError = result as BadRequestError[]; expect(resultOrError[0]).toEqual({ - author: [], - actions: [], rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); - expect(resultOrError[1].message).toEqual( - 'Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "name",Invalid value "undefined" supplied to "severity",Invalid value "undefined" supplied to "type",Invalid value "undefined" supplied to "rule_id"' + expect(resultOrError[1].message).toContain( + 'Invalid value "undefined" supplied to "name",Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "severity"' ); expect(resultOrError[2]).toEqual({ - author: [], - actions: [], rule_id: 'rule-2', output_index: '.siem-signals', risk_score: 50, - risk_score_mapping: [], description: 'some description', from: 'now-5m', to: 'now', index: ['index-1'], name: 'some-name', severity: 'low', - severity_mapping: [], interval: '5m', type: 'query', - enabled: true, - false_positives: [], immutable: false, - max_signals: 100, - exceptions_list: [], - tags: [], - threat: [], - throttle: null, - references: [], - version: 1, }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts index 1b1508bb079e4..f9e2fdd1b6ba5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -27,7 +27,6 @@ import { importRuleValidateTypeDependents } from '../../../../common/detection_e import { importRulesSchema, ImportRulesSchema, - ImportRulesSchemaDecoded, } from '../../../../common/detection_engine/schemas/request/import_rules_schema'; import { parseNdjsonStrings, @@ -41,7 +40,7 @@ import { export const validateRulesStream = (): Transform => { return createMapStream<{ exceptions: Array<ImportExceptionsListSchema | ImportExceptionListItemSchema | Error>; - rules: Array<ImportRulesSchemaDecoded | Error>; + rules: Array<ImportRulesSchema | Error>; }>((items) => ({ exceptions: items.exceptions, rules: validateRules(items.rules), @@ -50,20 +49,20 @@ export const validateRulesStream = (): Transform => { export const validateRules = ( rules: Array<ImportRulesSchema | Error> -): Array<ImportRulesSchemaDecoded | Error> => { +): Array<ImportRulesSchema | Error> => { return rules.map((obj: ImportRulesSchema | Error) => { if (!(obj instanceof Error)) { const decoded = importRulesSchema.decode(obj); const checked = exactCheck(obj, decoded); - const onLeft = (errors: t.Errors): BadRequestError | ImportRulesSchemaDecoded => { + const onLeft = (errors: t.Errors): BadRequestError | ImportRulesSchema => { return new BadRequestError(formatErrors(errors).join()); }; - const onRight = (schema: ImportRulesSchema): BadRequestError | ImportRulesSchemaDecoded => { + const onRight = (schema: ImportRulesSchema): BadRequestError | ImportRulesSchema => { const validationErrors = importRuleValidateTypeDependents(schema); if (validationErrors.length) { return new BadRequestError(validationErrors.join()); } else { - return schema as ImportRulesSchemaDecoded; + return schema; } }; return pipe(checked, fold(onLeft, onRight)); @@ -82,7 +81,7 @@ export const validateRules = ( export const sortImports = (): Transform => { return createReduceStream<{ exceptions: Array<ImportExceptionsListSchema | ImportExceptionListItemSchema | Error>; - rules: Array<ImportRulesSchemaDecoded | Error>; + rules: Array<ImportRulesSchema | Error>; }>( (acc, importItem) => { if (has('list_id', importItem) || has('item_id', importItem) || has('entries', importItem)) { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts index 2d92731dbbdfd..433366b69a9da 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts @@ -7,7 +7,7 @@ import { getPrepackagedRules } from './get_prepackaged_rules'; import { isEmpty } from 'lodash/fp'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; describe('get_existing_prepackaged_rules', () => { test('should not throw any errors with the existing checked in pre-packaged rules', () => { @@ -16,7 +16,7 @@ describe('get_existing_prepackaged_rules', () => { test('no rule should have the same rule_id as another rule_id', () => { const prePackagedRules = getPrepackagedRules(); - let existingRuleIds: AddPrepackagedRulesSchemaDecoded[] = []; + let existingRuleIds: AddPrepackagedRulesSchema[] = []; prePackagedRules.forEach((rule) => { const foundDuplicate = existingRuleIds.reduce((accum, existingRule) => { if (existingRule.rule_id === rule.rule_id) { @@ -35,17 +35,13 @@ describe('get_existing_prepackaged_rules', () => { test('should throw an exception if a pre-packaged rule is not valid', () => { // @ts-expect-error intentionally invalid argument - expect(() => getPrepackagedRules([{ not_valid_made_up_key: true }])).toThrow( - 'name: "(rule name unknown)", rule_id: "(rule rule_id unknown)" within the folder rules/prepackaged_rules is not a valid detection engine rule. Expect the system to not work with pre-packaged rules until this rule is fixed or the file is removed. Error is: Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "name",Invalid value "undefined" supplied to "severity",Invalid value "undefined" supplied to "type",Invalid value "undefined" supplied to "rule_id",Invalid value "undefined" supplied to "version", Full rule contents are:\n{\n "not_valid_made_up_key": true\n}' - ); + expect(() => getPrepackagedRules([{ not_valid_made_up_key: true }])).toThrow(); }); test('should throw an exception with a message having rule_id and name in it', () => { expect(() => // @ts-expect-error intentionally invalid argument getPrepackagedRules([{ name: 'rule name', rule_id: 'id-123' }]) - ).toThrow( - 'name: "rule name", rule_id: "id-123" within the folder rules/prepackaged_rules is not a valid detection engine rule. Expect the system to not work with pre-packaged rules until this rule is fixed or the file is removed. Error is: Invalid value "undefined" supplied to "description",Invalid value "undefined" supplied to "risk_score",Invalid value "undefined" supplied to "severity",Invalid value "undefined" supplied to "type",Invalid value "undefined" supplied to "version", Full rule contents are:\n{\n "name": "rule name",\n "rule_id": "id-123"\n}' - ); + ).toThrow(); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts index cc70dd1008de7..795cff421b4ca 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_prepackaged_rules.ts @@ -14,7 +14,6 @@ import { SavedObjectAttributes } from '@kbn/core/types'; import { addPrepackagedRulesSchema, AddPrepackagedRulesSchema, - AddPrepackagedRulesSchemaDecoded, } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; // TODO: convert rules files to TS and add explicit type definitions @@ -30,12 +29,12 @@ import { ConfigType } from '../../../config'; */ export const validateAllPrepackagedRules = ( rules: AddPrepackagedRulesSchema[] -): AddPrepackagedRulesSchemaDecoded[] => { +): AddPrepackagedRulesSchema[] => { return rules.map((rule) => { const decoded = addPrepackagedRulesSchema.decode(rule); const checked = exactCheck(rule, decoded); - const onLeft = (errors: t.Errors): AddPrepackagedRulesSchemaDecoded => { + const onLeft = (errors: t.Errors): AddPrepackagedRulesSchema => { const ruleName = rule.name ? rule.name : '(rule name unknown)'; const ruleId = rule.rule_id ? rule.rule_id : '(rule rule_id unknown)'; throw new BadRequestError( @@ -48,8 +47,8 @@ export const validateAllPrepackagedRules = ( ); }; - const onRight = (schema: AddPrepackagedRulesSchema): AddPrepackagedRulesSchemaDecoded => { - return schema as AddPrepackagedRulesSchemaDecoded; + const onRight = (schema: AddPrepackagedRulesSchema): AddPrepackagedRulesSchema => { + return schema as AddPrepackagedRulesSchema; }; return pipe(checked, fold(onLeft, onRight)); }); @@ -60,12 +59,12 @@ export const validateAllPrepackagedRules = ( */ export const validateAllRuleSavedObjects = ( rules: Array<IRuleAssetSOAttributes & SavedObjectAttributes> -): AddPrepackagedRulesSchemaDecoded[] => { +): AddPrepackagedRulesSchema[] => { return rules.map((rule) => { const decoded = addPrepackagedRulesSchema.decode(rule); const checked = exactCheck(rule, decoded); - const onLeft = (errors: t.Errors): AddPrepackagedRulesSchemaDecoded => { + const onLeft = (errors: t.Errors): AddPrepackagedRulesSchema => { const ruleName = rule.name ? rule.name : '(rule name unknown)'; const ruleId = rule.rule_id ? rule.rule_id : '(rule rule_id unknown)'; throw new BadRequestError( @@ -78,8 +77,8 @@ export const validateAllRuleSavedObjects = ( ); }; - const onRight = (schema: AddPrepackagedRulesSchema): AddPrepackagedRulesSchemaDecoded => { - return schema as AddPrepackagedRulesSchemaDecoded; + const onRight = (schema: AddPrepackagedRulesSchema): AddPrepackagedRulesSchema => { + return schema as AddPrepackagedRulesSchema; }; return pipe(checked, fold(onLeft, onRight)); }); @@ -90,7 +89,7 @@ export const validateAllRuleSavedObjects = ( */ export const getFleetInstalledRules = async ( client: RuleAssetSavedObjectsClient -): Promise<AddPrepackagedRulesSchemaDecoded[]> => { +): Promise<AddPrepackagedRulesSchema[]> => { const fleetResponse = await client.all(); const fleetRules = fleetResponse.map((so) => so.attributes); return validateAllRuleSavedObjects(fleetRules); @@ -99,7 +98,7 @@ export const getFleetInstalledRules = async ( export const getPrepackagedRules = ( // @ts-expect-error mock data is too loosely typed rules: AddPrepackagedRulesSchema[] = rawRules -): AddPrepackagedRulesSchemaDecoded[] => { +): AddPrepackagedRulesSchema[] => { return validateAllPrepackagedRules(rules); }; @@ -107,7 +106,7 @@ export const getLatestPrepackagedRules = async ( client: RuleAssetSavedObjectsClient, prebuiltRulesFromFileSystem: ConfigType['prebuiltRulesFromFileSystem'], prebuiltRulesFromSavedObjects: ConfigType['prebuiltRulesFromSavedObjects'] -): Promise<AddPrepackagedRulesSchemaDecoded[]> => { +): Promise<AddPrepackagedRulesSchema[]> => { // build a map of the most recent version of each rule const prepackaged = prebuiltRulesFromFileSystem ? getPrepackagedRules() : []; const ruleMap = new Map(prepackaged.map((r) => [r.rule_id, r])); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts index 5fcc0aec0f15e..ac86aa6dd860a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.test.ts @@ -7,9 +7,8 @@ import { getRulesToInstall } from './get_rules_to_install'; import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; +import { getAddPrepackagedRulesSchemaMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request'; describe('get_rules_to_install', () => { test('should return empty array if both rule sets are empty', () => { @@ -18,8 +17,7 @@ describe('get_rules_to_install', () => { }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); @@ -29,8 +27,7 @@ describe('get_rules_to_install', () => { }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getRuleMock(getQueryRuleParams()); @@ -40,12 +37,10 @@ describe('get_rules_to_install', () => { }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.rule_id = 'rule-2'; const installedRule = getRuleMock(getQueryRuleParams()); @@ -55,16 +50,13 @@ describe('get_rules_to_install', () => { }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = - getAddPrepackagedRulesSchemaDecodedMock() as AddPrepackagedRulesSchemaDecoded; + const ruleFromFileSystem3 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem3.rule_id = 'rule-3'; const installedRule = getRuleMock(getQueryRuleParams()); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts index a9e22562606a9..c930a4493b513 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_install.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { RuleAlertType } from './types'; export const getRulesToInstall = ( - rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[], + rulesFromFileSystem: AddPrepackagedRulesSchema[], installedRules: RuleAlertType[] ) => { return rulesFromFileSystem.filter( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts index a1d8458488486..986d68f0d3ffc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.test.ts @@ -7,7 +7,7 @@ import { filterInstalledRules, getRulesToUpdate, mergeExceptionLists } from './get_rules_to_update'; import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; +import { getAddPrepackagedRulesSchemaMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; describe('get_rules_to_update', () => { @@ -17,7 +17,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the rule_id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -29,7 +29,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the version of file system rule is less than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -41,7 +41,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the version of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -53,7 +53,7 @@ describe('get_rules_to_update', () => { }); test('should return the rule to update if the version of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -67,7 +67,7 @@ describe('get_rules_to_update', () => { }); test('should return 1 rule out of 2 to update if the version of file system rule is greater than the installed version of just one', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -86,11 +86,11 @@ describe('get_rules_to_update', () => { }); test('should return 2 rules out of 2 to update if the version of file system rule is greater than the installed version of both', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -112,7 +112,7 @@ describe('get_rules_to_update', () => { }); test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -134,7 +134,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -166,7 +166,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -195,7 +195,7 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; @@ -217,12 +217,12 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list for multiple rules', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.exceptions_list = []; ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -259,12 +259,12 @@ describe('get_rules_to_update', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list for mixed rules', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem2 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem2.exceptions_list = []; ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; @@ -315,7 +315,7 @@ describe('get_rules_to_update', () => { describe('filterInstalledRules', () => { test('should return "false" if the id of the two rules do not match', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -327,7 +327,7 @@ describe('filterInstalledRules', () => { }); test('should return "false" if the version of file system rule is less than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -339,7 +339,7 @@ describe('filterInstalledRules', () => { }); test('should return "false" if the version of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -351,7 +351,7 @@ describe('filterInstalledRules', () => { }); test('should return "true" to update if the version of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -367,7 +367,7 @@ describe('filterInstalledRules', () => { describe('mergeExceptionLists', () => { test('should add back an exception_list if it was removed by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -389,7 +389,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an additional exception_list if an additional one was added by the end user on an immutable rule during an upgrade', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -421,7 +421,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an existing exception_list if they are the same between the current installed one and the upgraded one', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = [ { id: 'endpoint_list', @@ -450,7 +450,7 @@ describe('mergeExceptionLists', () => { }); test('should not remove an existing exception_list if the rule has an empty exceptions list', () => { - const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaDecodedMock(); + const ruleFromFileSystem1 = getAddPrepackagedRulesSchemaMock(); ruleFromFileSystem1.exceptions_list = []; ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts index f0017c5e4bdb6..4a6e08e945879 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_rules_to_update.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { RuleAlertType } from './types'; /** @@ -16,7 +16,7 @@ import { RuleAlertType } from './types'; * @param installedRules The installed rules */ export const getRulesToUpdate = ( - rulesFromFileSystem: AddPrepackagedRulesSchemaDecoded[], + rulesFromFileSystem: AddPrepackagedRulesSchema[], installedRules: RuleAlertType[] ) => { return rulesFromFileSystem @@ -31,7 +31,7 @@ export const getRulesToUpdate = ( * @param installedRules The installed rules to compare against for updates */ export const filterInstalledRules = ( - ruleFromFileSystem: AddPrepackagedRulesSchemaDecoded, + ruleFromFileSystem: AddPrepackagedRulesSchema, installedRules: RuleAlertType[] ): boolean => { return installedRules.some((installedRule) => { @@ -49,9 +49,9 @@ export const filterInstalledRules = ( * @param installedRules The installed rules which might have user driven exceptions_lists */ export const mergeExceptionLists = ( - ruleFromFileSystem: AddPrepackagedRulesSchemaDecoded, + ruleFromFileSystem: AddPrepackagedRulesSchema, installedRules: RuleAlertType[] -): AddPrepackagedRulesSchemaDecoded => { +): AddPrepackagedRulesSchema => { if (ruleFromFileSystem.exceptions_list != null) { const installedRule = installedRules.find( (ruleToFind) => ruleToFind.params.ruleId === ruleFromFileSystem.rule_id diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts index 7ca213817cf0c..bc94a24ac3752 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -7,133 +7,21 @@ import { SanitizedRule, RuleTypeParams } from '@kbn/alerting-plugin/common'; import { RulesClient } from '@kbn/alerting-plugin/server'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { createRules } from './create_rules'; -import { PartialFilter } from '../types'; export const installPrepackagedRules = ( rulesClient: RulesClient, - rules: AddPrepackagedRulesSchemaDecoded[], - outputIndex: string + rules: AddPrepackagedRulesSchema[] ): Array<Promise<SanitizedRule<RuleTypeParams>>> => rules.reduce<Array<Promise<SanitizedRule<RuleTypeParams>>>>((acc, rule) => { - const { - anomaly_threshold: anomalyThreshold, - author, - building_block_type: buildingBlockType, - description, - enabled, - timestamp_field: timestampField, - event_category_override: eventCategoryOverride, - tiebreaker_field: tiebreakerField, - false_positives: falsePositives, - from, - query, - language, - license, - machine_learning_job_id: machineLearningJobId, - saved_id: savedId, - timeline_id: timelineId, - timeline_title: timelineTitle, - meta, - filters: filtersObject, - rule_id: ruleId, - index, - data_view_id: dataViewId, - interval, - max_signals: maxSignals, - related_integrations: relatedIntegrations, - required_fields: requiredFields, - risk_score: riskScore, - risk_score_mapping: riskScoreMapping, - rule_name_override: ruleNameOverride, - name, - setup, - severity, - severity_mapping: severityMapping, - tags, - to, - type, - threat, - threat_filters: threatFilters, - threat_mapping: threatMapping, - threat_language: threatLanguage, - concurrent_searches: concurrentSearches, - items_per_search: itemsPerSearch, - threat_query: threatQuery, - threat_index: threatIndex, - threat_indicator_path: threatIndicatorPath, - threshold, - timestamp_override: timestampOverride, - references, - namespace, - note, - version, - exceptions_list: exceptionsList, - } = rule; - // TODO: Fix these either with an is conversion or by better typing them within io-ts - const filters: PartialFilter[] | undefined = filtersObject as PartialFilter[]; - return [ ...acc, createRules({ rulesClient, - anomalyThreshold, - author, - buildingBlockType, - description, - enabled, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - immutable: true, // At the moment we force all prepackaged rules to be immutable - query, - language, - license, - machineLearningJobId, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - ruleId, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - setup, - severity, - severityMapping, - tags, - to, - type, - threat, - threatFilters, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - threatQuery, - threatIndex, - threatIndicatorPath, - threshold, - throttle: null, // At this time there is no pre-packaged actions - timestampOverride, - references, - namespace, - note, - version, - exceptionsList, - actions: [], // At this time there is no pre-packaged actions + params: rule, + immutable: true, + defaultEnabled: false, }), ]; }, []); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts deleted file mode 100644 index 9b45413ddcb0c..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.mock.ts +++ /dev/null @@ -1,120 +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 { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; - -import { PatchRulesOptions } from './types'; -import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; - -export const getPatchRulesOptionsMock = (): PatchRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: undefined, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - query: 'user.name: root or user.name: admin', - language: 'kuery', - license: 'Elastic License', - savedId: 'savedId-123', - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - meta: {}, - machineLearningJobId: undefined, - filters: [], - index: ['index-123'], - interval: '5m', - maxSignals: 100, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Query with a rule id', - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - throttle: null, - concurrentSearches: undefined, - itemsPerSearch: undefined, - timestampOverride: undefined, - to: 'now', - type: 'query', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], - rule: getRuleMock(getQueryRuleParams()), -}); - -export const getPatchMlRulesOptionsMock = (): PatchRulesOptions => ({ - author: ['Elastic'], - buildingBlockType: undefined, - rulesClient: rulesClientMock.create(), - anomalyThreshold: 55, - description: 'some description', - enabled: true, - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: ['false positive 1', 'false positive 2'], - from: 'now-6m', - query: undefined, - language: undefined, - license: 'Elastic License', - savedId: 'savedId-123', - timelineId: 'timelineid-123', - timelineTitle: 'timeline-title-123', - meta: {}, - machineLearningJobId: 'new_job_id', - filters: [], - index: ['index-123'], - interval: '5m', - maxSignals: 100, - riskScore: 80, - riskScoreMapping: [], - ruleNameOverride: undefined, - outputIndex: 'output-1', - name: 'Machine Learning Job', - severity: 'high', - severityMapping: [], - tags: [], - threat: [], - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - throttle: null, - concurrentSearches: undefined, - itemsPerSearch: undefined, - timestampOverride: undefined, - to: 'now', - type: 'machine_learning', - references: ['http://www.example.com'], - note: '# sample markdown', - version: 1, - exceptionsList: [], - actions: [], - rule: getRuleMock(getMlRuleParams()), -}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts index e2e09e130d5c3..d79f36f029a98 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.test.ts @@ -5,71 +5,67 @@ * 2.0. */ -import { RulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; +import { rulesClientMock } from '@kbn/alerting-plugin/server/rules_client.mock'; import { patchRules } from './patch_rules'; -import { getPatchRulesOptionsMock, getPatchMlRulesOptionsMock } from './patch_rules.mock'; -import { PatchRulesOptions } from './types'; import { getRuleMock } from '../routes/__mocks__/request_responses'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getMlRuleParams, getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { + getCreateMachineLearningRulesSchemaMock, + getCreateRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/rule_schemas.mock'; describe('patchRules', () => { it('should call rulesClient.disable if the rule was enabled and enabled is false', async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateRulesSchemaMock(), enabled: false, }; - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.disable).toHaveBeenCalledWith( + const rule = { + ...getRuleMock(getQueryRuleParams()), + enabled: true, + }; + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.disable).toHaveBeenCalledWith( expect.objectContaining({ - id: ruleOptions.rule?.id, + id: rule.id, }) ); }); it('should call rulesClient.enable if the rule was disabled and enabled is true', async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateRulesSchemaMock(), enabled: true, }; - if (ruleOptions.rule != null) { - ruleOptions.rule.enabled = false; - } - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.enable).toHaveBeenCalledWith( + const rule = { + ...getRuleMock(getQueryRuleParams()), + enabled: false, + }; + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.enable).toHaveBeenCalledWith( expect.objectContaining({ - id: ruleOptions.rule?.id, + id: rule.id, }) ); }); it('calls the rulesClient with legacy ML params', async () => { - const rulesOptionsMock = getPatchMlRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, - enabled: true, - }; - if (ruleOptions.rule != null) { - ruleOptions.rule.enabled = false; - } - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + const rulesClient = rulesClientMock.create(); + const params = getCreateMachineLearningRulesSchemaMock(); + const rule = getRuleMock(getMlRuleParams()); + rulesClient.update.mockResolvedValue(getRuleMock(getMlRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, - machineLearningJobId: ['new_job_id'], + anomalyThreshold: 58, + machineLearningJobId: ['typical-ml-job-id'], }), }), }) @@ -77,24 +73,19 @@ describe('patchRules', () => { }); it('calls the rulesClient with new ML params', async () => { - const rulesOptionsMock = getPatchMlRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, - machineLearningJobId: ['new_job_1', 'new_job_2'], - enabled: true, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateMachineLearningRulesSchemaMock(), + machine_learning_job_id: ['new_job_1', 'new_job_2'], }; - if (ruleOptions.rule != null) { - ruleOptions.rule.enabled = false; - } - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + const rule = getRuleMock(getMlRuleParams()); + rulesClient.update.mockResolvedValue(getRuleMock(getMlRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ params: expect.objectContaining({ - anomalyThreshold: 55, + anomalyThreshold: 58, machineLearningJobId: ['new_job_1', 'new_job_2'], }), }), @@ -104,9 +95,9 @@ describe('patchRules', () => { describe('regression tests', () => { it("updates the rule's actions if provided", async () => { - const rulesOptionsMock = getPatchRulesOptionsMock(); - const ruleOptions: PatchRulesOptions = { - ...rulesOptionsMock, + const rulesClient = rulesClientMock.create(); + const params = { + ...getCreateRulesSchemaMock(), actions: [ { action_type_id: '.slack', @@ -118,11 +109,10 @@ describe('patchRules', () => { }, ], }; - (rulesOptionsMock.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + const rule = getRuleMock(getQueryRuleParams()); + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ actions: [ @@ -141,25 +131,23 @@ describe('patchRules', () => { }); it('does not update actions if none are specified', async () => { - const ruleOptions = getPatchRulesOptionsMock(); - delete ruleOptions.actions; - if (ruleOptions.rule != null) { - ruleOptions.rule.actions = [ - { - actionTypeId: '.slack', - id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', - params: { - message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', - }, - group: 'default', + const rulesClient = rulesClientMock.create(); + const params = getCreateRulesSchemaMock(); + delete params.actions; + const rule = getRuleMock(getQueryRuleParams()); + rule.actions = [ + { + actionTypeId: '.slack', + id: '2933e581-d81c-4fe3-88fe-c57c6b8a5bfd', + params: { + message: 'Rule {{context.rule.name}} generated {{state.signals_count}} signals', }, - ]; - } - (ruleOptions.rulesClient as unknown as RulesClientMock).update.mockResolvedValue( - getRuleMock(getQueryRuleParams()) - ); - await patchRules(ruleOptions); - expect(ruleOptions.rulesClient.update).toHaveBeenCalledWith( + group: 'default', + }, + ]; + rulesClient.update.mockResolvedValue(getRuleMock(getQueryRuleParams())); + await patchRules({ rulesClient, params, rule }); + expect(rulesClient.update).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ actions: [ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts index 5fc98e8cd30fc..c6c768a36294a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/patch_rules.ts @@ -5,244 +5,47 @@ * 2.0. */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; -import { defaults } from 'lodash/fp'; import { PartialRule } from '@kbn/alerting-plugin/server'; -import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; -import { - normalizeMachineLearningJobIds, - normalizeThresholdObject, -} from '../../../../common/detection_engine/utils'; -import { internalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; +import { RuleParams } from '../schemas/rule_schemas'; import { PatchRulesOptions } from './types'; -import { - calculateInterval, - calculateName, - calculateVersion, - maybeMute, - removeUndefined, - transformToAlertThrottle, - transformToNotifyWhen, -} from './utils'; - -class PatchError extends Error { - public readonly statusCode: number; - constructor(message: string, statusCode: number) { - super(message); - this.statusCode = statusCode; - } -} +import { maybeMute } from './utils'; +import { convertPatchAPIToInternalSchema } from '../schemas/rule_converters'; export const patchRules = async ({ rulesClient, - author, - buildingBlockType, - description, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - enabled, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - from, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, rule, - name, - setup, - severity, - severityMapping, - tags, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - timestampOverride, - throttle, - to, - type, - references, - namespace, - note, - version, - exceptionsList, - anomalyThreshold, - machineLearningJobId, - actions, + params, }: PatchRulesOptions): Promise<PartialRule<RuleParams> | null> => { if (rule == null) { return null; } - const calculatedVersion = calculateVersion(rule.params.immutable, rule.params.version, { - author, - buildingBlockType, - description, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - from, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - setup, - severity, - severityMapping, - tags, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - timestampOverride, - to, - type, - references, - version, - namespace, - note, - exceptionsList, - anomalyThreshold, - machineLearningJobId, - }); - - const nextParams = defaults( - { - ...rule.params, - }, - { - author, - buildingBlockType, - description, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - index, - dataViewId, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - setup, - severity, - severityMapping, - threat, - threshold: threshold ? normalizeThresholdObject(threshold) : undefined, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - timestampOverride, - to, - type, - references, - namespace, - note, - version: calculatedVersion, - exceptionsList, - anomalyThreshold, - machineLearningJobId: machineLearningJobId - ? normalizeMachineLearningJobIds(machineLearningJobId) - : undefined, - } - ); - - const newRule = { - tags: tags ?? rule.tags, - name: calculateName({ updatedName: name, originalName: rule.name }), - schedule: { - interval: calculateInterval(interval, rule.schedule.interval), - }, - params: removeUndefined(nextParams), - actions: actions?.map(transformRuleToAlertAction) ?? rule.actions, - throttle: throttle !== undefined ? transformToAlertThrottle(throttle) : rule.throttle, - notifyWhen: throttle !== undefined ? transformToNotifyWhen(throttle) : rule.notifyWhen, - }; - - const [validated, errors] = validate(newRule, internalRuleUpdate); - if (errors != null || validated === null) { - throw new PatchError(`Applying patch would create invalid rule: ${errors}`, 400); - } + const patchedRule = convertPatchAPIToInternalSchema(params, rule); const update = await rulesClient.update({ id: rule.id, - data: validated, + data: patchedRule, }); - if (throttle !== undefined) { - await maybeMute({ rulesClient, muteAll: rule.muteAll, throttle, id: update.id }); + if (params.throttle !== undefined) { + await maybeMute({ + rulesClient, + muteAll: rule.muteAll, + throttle: params.throttle, + id: update.id, + }); } - if (rule.enabled && enabled === false) { + if (rule.enabled && params.enabled === false) { await rulesClient.disable({ id: rule.id }); - } else if (!rule.enabled && enabled === true) { + } else if (!rule.enabled && params.enabled === true) { await rulesClient.enable({ id: rule.id }); } else { // enabled is null or undefined and we do not touch the rule } - if (enabled != null) { - return { ...update, enabled }; + if (params.enabled != null) { + return { ...update, enabled: params.enabled }; } else { return update; } 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 3d5b8849fc6b1..da4fb4499b13e 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 @@ -8,102 +8,27 @@ import { Readable } from 'stream'; import { SavedObjectAttributes, SavedObjectsClientContract } from '@kbn/core/server'; -import type { - MachineLearningJobIdOrUndefined, - From, - FromOrUndefined, - RiskScore, - RiskScoreMapping, - RiskScoreMappingOrUndefined, - RiskScoreOrUndefined, - ThreatIndexOrUndefined, - ThreatQueryOrUndefined, - ThreatMappingOrUndefined, - ThreatFiltersOrUndefined, - ThreatLanguageOrUndefined, - ConcurrentSearchesOrUndefined, - ItemsPerSearchOrUndefined, - ThreatIndicatorPathOrUndefined, - Threats, - ThreatsOrUndefined, - TypeOrUndefined, - Type, - LanguageOrUndefined, - SeverityMapping, - SeverityMappingOrUndefined, - SeverityOrUndefined, - Severity, - MaxSignalsOrUndefined, - MaxSignals, - ThrottleOrUndefinedOrNull, - ThrottleOrNull, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import type { VersionOrUndefined, Version } from '@kbn/securitysolution-io-ts-types'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; -import type { ListArrayOrUndefined, ListArray } from '@kbn/securitysolution-io-ts-list-types'; import { RulesClient, PartialRule, BulkEditOperation } from '@kbn/alerting-plugin/server'; import { SanitizedRule } from '@kbn/alerting-plugin/common'; import { UpdateRulesSchema } from '../../../../common/detection_engine/schemas/request'; -import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { - FalsePositives, - RuleId, - Immutable, - DescriptionOrUndefined, - Interval, - OutputIndex, - Name, - Tags, - To, - References, - AnomalyThresholdOrUndefined, - QueryOrUndefined, - SavedIdOrUndefined, - TimelineIdOrUndefined, - TimelineTitleOrUndefined, - IndexOrUndefined, - NoteOrUndefined, - MetaOrUndefined, - Description, - Enabled, Id, IdOrUndefined, RuleIdOrUndefined, - EnabledOrUndefined, - FalsePositivesOrUndefined, - OutputIndexOrUndefined, - IntervalOrUndefined, - NameOrUndefined, - TagsOrUndefined, - ToOrUndefined, - ThresholdOrUndefined, - ReferencesOrUndefined, PerPageOrUndefined, PageOrUndefined, SortFieldOrUndefined, QueryFilterOrUndefined, FieldsOrUndefined, SortOrderOrUndefined, - Author, - AuthorOrUndefined, - LicenseOrUndefined, - TimestampOverrideOrUndefined, - BuildingBlockTypeOrUndefined, - RuleNameOverrideOrUndefined, - TimestampFieldOrUndefined, - EventCategoryOverrideOrUndefined, - TiebreakerFieldOrUndefined, - NamespaceOrUndefined, - DataViewIdOrUndefined, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, } from '../../../../common/detection_engine/schemas/common'; -import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { IRuleExecutionLogForRoutes } from '../rule_execution_log'; +import { CreateRulesSchema } from '../../../../common/detection_engine/schemas/request/rule_schemas'; +import { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/patch_rules_schema'; export type RuleAlertType = SanitizedRule<RuleParams>; @@ -143,136 +68,26 @@ export const isAlertType = ( return ruleTypeValues.includes(partialAlert.alertTypeId as string); }; -export interface CreateRulesOptions { +export interface CreateRulesOptions<T extends CreateRulesSchema = CreateRulesSchema> { rulesClient: RulesClient; - anomalyThreshold: AnomalyThresholdOrUndefined; - author: Author; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: Description; - enabled: Enabled; - timestampField: TimestampFieldOrUndefined; - eventCategoryOverride: EventCategoryOverrideOrUndefined; - tiebreakerField: TiebreakerFieldOrUndefined; - falsePositives: FalsePositives; - from: From; - query: QueryOrUndefined; - language: LanguageOrUndefined; - savedId: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId: MachineLearningJobIdOrUndefined; - filters: PartialFilter[]; - ruleId: RuleId; - immutable: Immutable; - index: IndexOrUndefined; - dataViewId: DataViewIdOrUndefined; - interval: Interval; - license: LicenseOrUndefined; - maxSignals: MaxSignals; - relatedIntegrations: RelatedIntegrationArray | undefined; - requiredFields: RequiredFieldArray | undefined; - riskScore: RiskScore; - riskScoreMapping: RiskScoreMapping; - ruleNameOverride: RuleNameOverrideOrUndefined; - outputIndex: OutputIndex; - name: Name; - setup: SetupGuide | undefined; - severity: Severity; - severityMapping: SeverityMapping; - tags: Tags; - threat: Threats; - threshold: ThresholdOrUndefined; - threatFilters: ThreatFiltersOrUndefined; - threatIndex: ThreatIndexOrUndefined; - threatIndicatorPath: ThreatIndicatorPathOrUndefined; - threatQuery: ThreatQueryOrUndefined; - threatMapping: ThreatMappingOrUndefined; - concurrentSearches: ConcurrentSearchesOrUndefined; - itemsPerSearch: ItemsPerSearchOrUndefined; - threatLanguage: ThreatLanguageOrUndefined; - throttle: ThrottleOrNull; - timestampOverride: TimestampOverrideOrUndefined; - to: To; - type: Type; - references: References; - note: NoteOrUndefined; - version: Version; - exceptionsList: ListArray; - actions: RuleAlertAction[]; - namespace?: NamespaceOrUndefined; + params: T; id?: string; + immutable?: boolean; + defaultEnabled?: boolean; } export interface UpdateRulesOptions { rulesClient: RulesClient; - defaultOutputIndex: string; existingRule: RuleAlertType | null | undefined; ruleUpdate: UpdateRulesSchema; } -export interface PatchRulesOptions extends Partial<PatchRulesFieldsOptions> { +export interface PatchRulesOptions { rulesClient: RulesClient; + params: PatchRulesSchema; rule: RuleAlertType | null | undefined; } -interface PatchRulesFieldsOptions { - anomalyThreshold: AnomalyThresholdOrUndefined; - author: AuthorOrUndefined; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: DescriptionOrUndefined; - enabled: EnabledOrUndefined; - timestampField: TimestampFieldOrUndefined; - eventCategoryOverride: EventCategoryOverrideOrUndefined; - tiebreakerField: TiebreakerFieldOrUndefined; - falsePositives: FalsePositivesOrUndefined; - from: FromOrUndefined; - query: QueryOrUndefined; - language: LanguageOrUndefined; - savedId: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId: MachineLearningJobIdOrUndefined; - filters: PartialFilter[]; - index: IndexOrUndefined; - dataViewId: DataViewIdOrUndefined; - interval: IntervalOrUndefined; - license: LicenseOrUndefined; - maxSignals: MaxSignalsOrUndefined; - relatedIntegrations: RelatedIntegrationArray | undefined; - requiredFields: RequiredFieldArray | undefined; - riskScore: RiskScoreOrUndefined; - riskScoreMapping: RiskScoreMappingOrUndefined; - ruleNameOverride: RuleNameOverrideOrUndefined; - outputIndex: OutputIndexOrUndefined; - name: NameOrUndefined; - setup: SetupGuide | undefined; - severity: SeverityOrUndefined; - severityMapping: SeverityMappingOrUndefined; - tags: TagsOrUndefined; - threat: ThreatsOrUndefined; - itemsPerSearch: ItemsPerSearchOrUndefined; - concurrentSearches: ConcurrentSearchesOrUndefined; - threshold: ThresholdOrUndefined; - threatFilters: ThreatFiltersOrUndefined; - threatIndex: ThreatIndexOrUndefined; - threatIndicatorPath: ThreatIndicatorPathOrUndefined; - threatQuery: ThreatQueryOrUndefined; - threatMapping: ThreatMappingOrUndefined; - threatLanguage: ThreatLanguageOrUndefined; - throttle: ThrottleOrUndefinedOrNull; - timestampOverride: TimestampOverrideOrUndefined; - to: ToOrUndefined; - type: TypeOrUndefined; - references: ReferencesOrUndefined; - note: NoteOrUndefined; - version: VersionOrUndefined; - exceptionsList: ListArrayOrUndefined; - actions: RuleAlertAction[] | undefined; - namespace?: NamespaceOrUndefined; -} - export interface ReadRuleOptions { rulesClient: RulesClient; id: IdOrUndefined; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts index 2a54ce1e976d4..dc28f2184323c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.test.ts @@ -10,10 +10,13 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { getRuleMock, getFindResultWithSingleHit } from '../routes/__mocks__/request_responses'; import { updatePrepackagedRules } from './update_prepacked_rules'; import { patchRules } from './patch_rules'; -import { getAddPrepackagedRulesSchemaDecodedMock } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; +import { + getAddPrepackagedRulesSchemaMock, + getAddPrepackagedThreatMatchRulesSchemaMock, +} from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema.mock'; import { ruleExecutionLogMock } from '../rule_execution_log/__mocks__'; import { legacyMigrate } from './utils'; -import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; +import { getQueryRuleParams, getThreatRuleParams } from '../schemas/rule_schemas.mock'; jest.mock('./patch_rules'); @@ -47,27 +50,29 @@ describe('updatePrepackagedRules', () => { params: {}, }, ]; - const outputIndex = 'outputIndex'; - const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock(); + const prepackagedRule = getAddPrepackagedRulesSchemaMock(); rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); await updatePrepackagedRules( rulesClient, savedObjectsClient, [{ ...prepackagedRule, actions }], - outputIndex, ruleExecutionLog ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - actions: undefined, + params: expect.objectContaining({ + actions: undefined, + }), }) ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - enabled: undefined, + params: expect.objectContaining({ + enabled: undefined, + }), }) ); }); @@ -78,32 +83,41 @@ describe('updatePrepackagedRules', () => { threat_indicator_path: 'test.path', threat_query: 'threat:*', }; - const prepackagedRule = getAddPrepackagedRulesSchemaDecodedMock(); - rulesClient.find.mockResolvedValue(getFindResultWithSingleHit()); + const prepackagedRule = getAddPrepackagedThreatMatchRulesSchemaMock(); + rulesClient.find.mockResolvedValue({ + ...getFindResultWithSingleHit(), + data: [getRuleMock(getThreatRuleParams())], + }); + (legacyMigrate as jest.Mock).mockResolvedValue(getRuleMock(getThreatRuleParams())); await updatePrepackagedRules( rulesClient, savedObjectsClient, [{ ...prepackagedRule, ...updatedThreatParams }], - 'output-index', ruleExecutionLog ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - threatIndicatorPath: 'test.path', + params: expect.objectContaining({ + threat_indicator_path: 'test.path', + }), }) ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - threatIndex: ['test-index'], + params: expect.objectContaining({ + threat_index: ['test-index'], + }), }) ); expect(patchRules).toHaveBeenCalledWith( expect.objectContaining({ - threatQuery: 'threat:*', + params: expect.objectContaining({ + threat_query: 'threat:*', + }), }) ); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts index f64383d36e11e..0cc97ee6b1194 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -8,11 +8,10 @@ import { chunk } from 'lodash/fp'; import { SavedObjectsClientContract } from '@kbn/core/server'; import { RulesClient, PartialRule } from '@kbn/alerting-plugin/server'; -import { AddPrepackagedRulesSchemaDecoded } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; +import { AddPrepackagedRulesSchema } from '../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema'; import { MAX_RULES_TO_UPDATE_IN_PARALLEL } from '../../../../common/constants'; import { patchRules } from './patch_rules'; import { readRules } from './read_rules'; -import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { legacyMigrate } from './utils'; import { deleteRules } from './delete_rules'; @@ -28,13 +27,11 @@ import { transformAlertToRuleAction } from '../../../../common/detection_engine/ * @param rulesClient Alerting client * @param spaceId Current user spaceId * @param rules The rules to apply the update for - * @param outputIndex The output index to apply the update to. */ export const updatePrepackagedRules = async ( rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, - rules: AddPrepackagedRulesSchemaDecoded[], - outputIndex: string, + rules: AddPrepackagedRulesSchema[], ruleExecutionLog: IRuleExecutionLogForRoutes ): Promise<void> => { const ruleChunks = chunk(MAX_RULES_TO_UPDATE_IN_PARALLEL, rules); @@ -43,7 +40,6 @@ export const updatePrepackagedRules = async ( rulesClient, savedObjectsClient, ruleChunk, - outputIndex, ruleExecutionLog ); await Promise.all(rulePromises); @@ -55,80 +51,21 @@ export const updatePrepackagedRules = async ( * @param rulesClient Alerting client * @param spaceId Current user spaceId * @param rules The rules to apply the update for - * @param outputIndex The output index to apply the update to. * @returns Promise of what was updated. */ export const createPromises = ( rulesClient: RulesClient, savedObjectsClient: SavedObjectsClientContract, - rules: AddPrepackagedRulesSchemaDecoded[], - outputIndex: string, + rules: AddPrepackagedRulesSchema[], ruleExecutionLog: IRuleExecutionLogForRoutes ): Array<Promise<PartialRule<RuleParams> | null>> => { return rules.map(async (rule) => { - const { - author, - building_block_type: buildingBlockType, - description, - timestamp_field: timestampField, - event_category_override: eventCategoryOverride, - tiebreaker_field: tiebreakerField, - false_positives: falsePositives, - from, - query, - language, - license, - saved_id: savedId, - meta, - filters: filtersObject, - rule_id: ruleId, - index, - data_view_id: dataViewId, - interval, - max_signals: maxSignals, - related_integrations: relatedIntegrations, - required_fields: requiredFields, - risk_score: riskScore, - risk_score_mapping: riskScoreMapping, - rule_name_override: ruleNameOverride, - name, - setup, - severity, - severity_mapping: severityMapping, - tags, - to, - type, - threat, - threshold, - threat_filters: threatFilters, - threat_index: threatIndex, - threat_indicator_path: threatIndicatorPath, - threat_query: threatQuery, - threat_mapping: threatMapping, - threat_language: threatLanguage, - concurrent_searches: concurrentSearches, - items_per_search: itemsPerSearch, - timestamp_override: timestampOverride, - references, - version, - note, - throttle, - anomaly_threshold: anomalyThreshold, - timeline_id: timelineId, - timeline_title: timelineTitle, - machine_learning_job_id: machineLearningJobId, - exceptions_list: exceptionsList, - } = rule; - const existingRule = await readRules({ rulesClient, - ruleId, + ruleId: rule.rule_id, id: undefined, }); - // TODO: Fix these either with an is conversion or by better typing them within io-ts - const filters: PartialFilter[] | undefined = filtersObject as PartialFilter[]; - const migratedRule = await legacyMigrate({ rulesClient, savedObjectsClient, @@ -136,138 +73,39 @@ export const createPromises = ( }); if (!migratedRule) { - throw new PrepackagedRulesError(`Failed to find rule ${ruleId}`, 500); + throw new PrepackagedRulesError(`Failed to find rule ${rule.rule_id}`, 500); } // If we're trying to change the type of a prepackaged rule, we need to delete the old one // and replace it with the new rule, keeping the enabled setting, actions, throttle, id, // and exception lists from the old rule - if (type !== migratedRule.params.type) { + if (rule.type !== migratedRule.params.type) { await deleteRules({ ruleId: migratedRule.id, rulesClient, ruleExecutionLog, }); - return (await createRules({ - id: migratedRule.id, + return createRules({ rulesClient, - anomalyThreshold, - author, - buildingBlockType, - description, - enabled: migratedRule.enabled, // Enabled comes from existing rule - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - immutable: true, // At the moment we force all prepackaged rules to be immutable - query, - language, - license, - machineLearningJobId, - outputIndex, - savedId, - timelineId, - timelineTitle, - meta, - filters, - ruleId, - index, - dataViewId, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - setup, - severity, - severityMapping, - tags, - to, - type, - threat, - threatFilters, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - threatQuery, - threatIndex, - threatIndicatorPath, - threshold, - throttle: migratedRule.throttle, // Throttle comes from the existing rule - timestampOverride, - references, - note, - version, - // The exceptions list passed in to this function has already been merged with the exceptions list of - // the existing rule - exceptionsList, - actions: migratedRule.actions.map(transformAlertToRuleAction), // Actions come from the existing rule - })) as PartialRule<RuleParams>; // TODO: Replace AddPrepackagedRulesSchema with type specific rules schema so we can clean up these types + params: { + ...rule, + // Force the prepackaged rule to use the enabled state from the existing rule, + // regardless of what the prepackaged rule says + enabled: migratedRule.enabled, + actions: migratedRule.actions.map(transformAlertToRuleAction), + }, + }); } else { - // Note: we do not pass down enabled as we do not want to suddenly disable - // or enable rules on the user when they were not expecting it if a rule updates return patchRules({ rulesClient, - author, - buildingBlockType, - description, - timestampField, - eventCategoryOverride, - tiebreakerField, - falsePositives, - from, - query, - language, - license, - outputIndex, rule: migratedRule, - savedId, - meta, - filters, - index, - interval, - maxSignals, - relatedIntegrations, - requiredFields, - riskScore, - riskScoreMapping, - ruleNameOverride, - name, - setup, - severity, - severityMapping, - tags, - timestampOverride, - to, - type, - threat, - threshold, - threatFilters, - threatIndex, - threatIndicatorPath, - threatQuery, - threatMapping, - threatLanguage, - concurrentSearches, - itemsPerSearch, - references, - version, - note, - anomalyThreshold, - enabled: undefined, - timelineId, - timelineTitle, - machineLearningJobId, - exceptionsList, - throttle, - actions: undefined, + params: { + ...rule, + // Force enabled to use the enabled state from the existing rule by passing in undefined to patchRules + enabled: undefined, + actions: undefined, + }, }); } }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts index 9f19e68443c45..bdaf757cb086e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/update_rules.mock.ts @@ -6,7 +6,6 @@ */ import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { getUpdateMachineLearningSchemaMock, getUpdateRulesSchemaMock, @@ -15,21 +14,13 @@ import { getRuleMock } from '../routes/__mocks__/request_responses'; import { getQueryRuleParams } from '../schemas/rule_schemas.mock'; export const getUpdateRulesOptionsMock = () => ({ - spaceId: 'default', rulesClient: rulesClientMock.create(), - savedObjectsClient: savedObjectsClientMock.create(), - defaultOutputIndex: '.siem-signals-default', existingRule: getRuleMock(getQueryRuleParams()), - migratedRule: getRuleMock(getQueryRuleParams()), ruleUpdate: getUpdateRulesSchemaMock(), }); export const getUpdateMlRulesOptionsMock = () => ({ - spaceId: 'default', rulesClient: rulesClientMock.create(), - savedObjectsClient: savedObjectsClientMock.create(), - defaultOutputIndex: '.siem-signals-default', existingRule: getRuleMock(getQueryRuleParams()), - migratedRule: getRuleMock(getQueryRuleParams()), ruleUpdate: getUpdateMachineLearningSchemaMock(), }); 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 7c981a5481ff9..4e077097680af 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 @@ -6,27 +6,17 @@ */ /* eslint-disable complexity */ -import { validate } from '@kbn/securitysolution-io-ts-utils'; import { PartialRule } from '@kbn/alerting-plugin/server'; import { DEFAULT_MAX_SIGNALS } from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { UpdateRulesOptions } from './types'; import { typeSpecificSnakeToCamel } from '../schemas/rule_converters'; -import { internalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; +import { InternalRuleUpdate, RuleParams } from '../schemas/rule_schemas'; import { maybeMute, transformToAlertThrottle, transformToNotifyWhen } from './utils'; -class UpdateError extends Error { - public readonly statusCode: number; - constructor(message: string, statusCode: number) { - super(message); - this.statusCode = statusCode; - } -} - export const updateRules = async ({ rulesClient, - defaultOutputIndex, existingRule, ruleUpdate, }: UpdateRulesOptions): Promise<PartialRule<RuleParams> | null> => { @@ -36,7 +26,7 @@ export const updateRules = async ({ const typeSpecificParams = typeSpecificSnakeToCamel(ruleUpdate); const enabled = ruleUpdate.enabled ?? true; - const newInternalRule = { + const newInternalRule: InternalRuleUpdate = { name: ruleUpdate.name, tags: ruleUpdate.tags ?? [], params: { @@ -49,7 +39,7 @@ export const updateRules = async ({ // Unlike the create route, immutable comes from the existing rule here immutable: existingRule.params.immutable, license: ruleUpdate.license, - outputIndex: ruleUpdate.output_index ?? defaultOutputIndex, + outputIndex: ruleUpdate.output_index ?? '', timelineId: ruleUpdate.timeline_id, timelineTitle: ruleUpdate.timeline_title, meta: ruleUpdate.meta, @@ -83,14 +73,9 @@ export const updateRules = async ({ notifyWhen: transformToNotifyWhen(ruleUpdate.throttle), }; - const [validated, errors] = validate(newInternalRule, internalRuleUpdate); - if (errors != null || validated === null) { - throw new UpdateError(`Applying update would create invalid rule: ${errors}`, 400); - } - const update = await rulesClient.update({ id: existingRule.id, - data: validated, + data: newInternalRule, }); await maybeMute({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts index e59b135b636e1..163fc81691739 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/utils.test.ts @@ -6,9 +6,6 @@ */ import { - calculateInterval, - calculateVersion, - calculateName, transformToNotifyWhen, transformToAlertThrottle, transformFromAlertThrottle, @@ -88,221 +85,6 @@ const getRuleLegacyActions = (): SanitizedRule<RuleParams> => } as unknown as SanitizedRule<RuleParams>); describe('utils', () => { - describe('#calculateInterval', () => { - test('given a undefined interval, it returns the ruleInterval ', () => { - const interval = calculateInterval(undefined, '10m'); - expect(interval).toEqual('10m'); - }); - - test('given a undefined ruleInterval, it returns a undefined interval ', () => { - const interval = calculateInterval('10m', undefined); - expect(interval).toEqual('10m'); - }); - - test('given both an undefined ruleInterval and a undefined interval, it returns 5m', () => { - const interval = calculateInterval(undefined, undefined); - expect(interval).toEqual('5m'); - }); - }); - - describe('#calculateVersion', () => { - test('returning the same version number if given an immutable but no updated version number', () => { - expect( - calculateVersion(true, 1, { - author: [], - buildingBlockType: undefined, - dataViewId: undefined, - description: 'some description change', - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: undefined, - query: undefined, - language: undefined, - license: undefined, - outputIndex: undefined, - savedId: undefined, - timelineId: undefined, - timelineTitle: undefined, - meta: undefined, - filters: [], - from: undefined, - index: undefined, - interval: undefined, - maxSignals: undefined, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: undefined, - riskScoreMapping: undefined, - ruleNameOverride: undefined, - name: undefined, - setup: undefined, - severity: undefined, - severityMapping: undefined, - tags: undefined, - threat: undefined, - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - to: undefined, - timestampOverride: undefined, - type: undefined, - references: undefined, - version: undefined, - namespace: undefined, - note: undefined, - anomalyThreshold: undefined, - machineLearningJobId: undefined, - exceptionsList: [], - }) - ).toEqual(1); - }); - - test('returning an updated version number if given an immutable and an updated version number', () => { - expect( - calculateVersion(true, 2, { - author: [], - buildingBlockType: undefined, - dataViewId: undefined, - description: 'some description change', - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: undefined, - query: undefined, - language: undefined, - license: undefined, - outputIndex: undefined, - savedId: undefined, - timelineId: undefined, - timelineTitle: undefined, - meta: undefined, - filters: [], - from: undefined, - index: undefined, - interval: undefined, - maxSignals: undefined, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: undefined, - riskScoreMapping: undefined, - ruleNameOverride: undefined, - name: undefined, - setup: undefined, - severity: undefined, - severityMapping: undefined, - tags: undefined, - threat: undefined, - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - to: undefined, - timestampOverride: undefined, - type: undefined, - references: undefined, - version: undefined, - namespace: undefined, - note: undefined, - anomalyThreshold: undefined, - machineLearningJobId: undefined, - exceptionsList: [], - }) - ).toEqual(2); - }); - - test('returning an updated version number if not given an immutable but but an updated description', () => { - expect( - calculateVersion(false, 1, { - author: [], - buildingBlockType: undefined, - dataViewId: undefined, - description: 'some description change', - timestampField: undefined, - eventCategoryOverride: undefined, - tiebreakerField: undefined, - falsePositives: undefined, - query: undefined, - language: undefined, - license: undefined, - outputIndex: undefined, - savedId: undefined, - timelineId: undefined, - timelineTitle: undefined, - meta: undefined, - filters: [], - from: undefined, - index: undefined, - interval: undefined, - maxSignals: undefined, - relatedIntegrations: undefined, - requiredFields: undefined, - riskScore: undefined, - riskScoreMapping: undefined, - ruleNameOverride: undefined, - name: undefined, - setup: undefined, - severity: undefined, - severityMapping: undefined, - tags: undefined, - threat: undefined, - threshold: undefined, - threatFilters: undefined, - threatIndex: undefined, - threatIndicatorPath: undefined, - threatQuery: undefined, - threatMapping: undefined, - threatLanguage: undefined, - to: undefined, - timestampOverride: undefined, - concurrentSearches: undefined, - itemsPerSearch: undefined, - type: undefined, - references: undefined, - version: undefined, - namespace: undefined, - note: undefined, - anomalyThreshold: undefined, - machineLearningJobId: undefined, - exceptionsList: [], - }) - ).toEqual(2); - }); - }); - - describe('#calculateName', () => { - test('should return the updated name when it and originalName is there', () => { - const name = calculateName({ updatedName: 'updated', originalName: 'original' }); - expect(name).toEqual('updated'); - }); - - test('should return the updated name when originalName is undefined', () => { - const name = calculateName({ updatedName: 'updated', originalName: undefined }); - expect(name).toEqual('updated'); - }); - - test('should return the original name when updatedName is undefined', () => { - const name = calculateName({ updatedName: undefined, originalName: 'original' }); - expect(name).toEqual('original'); - }); - - test('should return untitled when both updatedName and originalName is undefined', () => { - const name = calculateName({ updatedName: undefined, originalName: undefined }); - expect(name).toEqual('untitled'); - }); - }); - describe('#transformToNotifyWhen', () => { test('"null" throttle returns "null" notify', () => { expect(transformToNotifyWhen(null)).toEqual(null); 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 35060927f3762..6f68e6b57f799 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,65 +5,10 @@ * 2.0. */ -import { pickBy, isEmpty } from 'lodash/fp'; -import type { - FromOrUndefined, - MachineLearningJobIdOrUndefined, - RiskScoreMappingOrUndefined, - RiskScoreOrUndefined, - ConcurrentSearchesOrUndefined, - ItemsPerSearchOrUndefined, - ThreatFiltersOrUndefined, - ThreatIndexOrUndefined, - ThreatIndicatorPathOrUndefined, - ThreatLanguageOrUndefined, - ThreatMappingOrUndefined, - ThreatQueryOrUndefined, - ThreatsOrUndefined, - TypeOrUndefined, - LanguageOrUndefined, - SeverityOrUndefined, - SeverityMappingOrUndefined, - MaxSignalsOrUndefined, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import type { ListArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; -import type { VersionOrUndefined } from '@kbn/securitysolution-io-ts-types'; +import { isEmpty } from 'lodash/fp'; import { SavedObjectReference } from '@kbn/core/server'; import { RuleAction, RuleNotifyWhenType, SanitizedRule } from '@kbn/alerting-plugin/common'; import { RulesClient } from '@kbn/alerting-plugin/server'; -import { - DescriptionOrUndefined, - AnomalyThresholdOrUndefined, - QueryOrUndefined, - SavedIdOrUndefined, - TimelineIdOrUndefined, - TimelineTitleOrUndefined, - IndexOrUndefined, - NoteOrUndefined, - MetaOrUndefined, - FalsePositivesOrUndefined, - OutputIndexOrUndefined, - IntervalOrUndefined, - NameOrUndefined, - TagsOrUndefined, - ToOrUndefined, - ThresholdOrUndefined, - ReferencesOrUndefined, - AuthorOrUndefined, - BuildingBlockTypeOrUndefined, - LicenseOrUndefined, - RuleNameOverrideOrUndefined, - TimestampOverrideOrUndefined, - TimestampFieldOrUndefined, - EventCategoryOverrideOrUndefined, - TiebreakerFieldOrUndefined, - NamespaceOrUndefined, - DataViewIdOrUndefined, - RelatedIntegrationArray, - RequiredFieldArray, - SetupGuide, -} from '../../../../common/detection_engine/schemas/common'; -import { PartialFilter } from '../types'; import { RuleParams } from '../schemas/rule_schemas'; import { NOTIFICATION_THROTTLE_NO_ACTIONS, @@ -81,128 +26,6 @@ import { transformAlertToRuleAction } from '../../../../common/detection_engine/ import { legacyRuleActionsSavedObjectType } from '../rule_actions/legacy_saved_object_mappings'; import { LegacyMigrateParams } from './types'; -export const calculateInterval = ( - interval: string | undefined, - ruleInterval: string | undefined -): string => { - if (interval != null) { - return interval; - } else if (ruleInterval != null) { - return ruleInterval; - } else { - return '5m'; - } -}; - -export interface UpdateProperties { - author: AuthorOrUndefined; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: DescriptionOrUndefined; - timestampField: TimestampFieldOrUndefined; - eventCategoryOverride: EventCategoryOverrideOrUndefined; - tiebreakerField: TiebreakerFieldOrUndefined; - falsePositives: FalsePositivesOrUndefined; - from: FromOrUndefined; - query: QueryOrUndefined; - language: LanguageOrUndefined; - license: LicenseOrUndefined; - savedId: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId: MachineLearningJobIdOrUndefined; - filters: PartialFilter[] | undefined; - index: IndexOrUndefined; - dataViewId: DataViewIdOrUndefined; - interval: IntervalOrUndefined; - maxSignals: MaxSignalsOrUndefined; - relatedIntegrations: RelatedIntegrationArray | undefined; - requiredFields: RequiredFieldArray | undefined; - riskScore: RiskScoreOrUndefined; - riskScoreMapping: RiskScoreMappingOrUndefined; - ruleNameOverride: RuleNameOverrideOrUndefined; - outputIndex: OutputIndexOrUndefined; - name: NameOrUndefined; - setup: SetupGuide | undefined; - severity: SeverityOrUndefined; - severityMapping: SeverityMappingOrUndefined; - tags: TagsOrUndefined; - threat: ThreatsOrUndefined; - threshold: ThresholdOrUndefined; - threatFilters: ThreatFiltersOrUndefined; - threatIndex: ThreatIndexOrUndefined; - threatIndicatorPath: ThreatIndicatorPathOrUndefined; - threatQuery: ThreatQueryOrUndefined; - threatMapping: ThreatMappingOrUndefined; - threatLanguage: ThreatLanguageOrUndefined; - concurrentSearches: ConcurrentSearchesOrUndefined; - itemsPerSearch: ItemsPerSearchOrUndefined; - timestampOverride: TimestampOverrideOrUndefined; - to: ToOrUndefined; - type: TypeOrUndefined; - references: ReferencesOrUndefined; - note: NoteOrUndefined; - version: VersionOrUndefined; - exceptionsList: ListArrayOrUndefined; - anomalyThreshold: AnomalyThresholdOrUndefined; - namespace: NamespaceOrUndefined; -} - -export const calculateVersion = ( - immutable: boolean, - currentVersion: number, - updateProperties: UpdateProperties -): number => { - // early return if we are pre-packaged/immutable rule to be safe. We are never responsible - // for changing the version number of an immutable. Immutables are only responsible for changing - // their own version number. This would be really bad if an immutable version number is bumped by us - // due to a bug, hence the extra check and early bail if that is detected. - if (immutable === true) { - if (updateProperties.version != null) { - // we are an immutable rule but we are asking to update the version number so go ahead - // and update it to what is asked. - return updateProperties.version; - } else { - // we are immutable and not asking to update the version number so return the existing version - return currentVersion; - } - } - - // white list all properties but the enabled/disabled flag. We don't want to auto-increment - // the version number if only the enabled/disabled flag is being set. Likewise if we get other - // properties we are not expecting such as updatedAt we do not to cause a version number bump - // on that either. - const removedNullValues = removeUndefined(updateProperties); - if (isEmpty(removedNullValues)) { - return currentVersion; - } else { - return currentVersion + 1; - } -}; - -export const removeUndefined = (obj: object) => { - return pickBy((value: unknown) => value != null, obj); -}; - -export const calculateName = ({ - updatedName, - originalName, -}: { - updatedName: string | undefined; - originalName: string | undefined; -}): string => { - if (updatedName != null) { - return updatedName; - } else if (originalName != null) { - return originalName; - } else { - // You really should never get to this point. This is a fail safe way to send back - // the name of "untitled" just in case a rule name became null or undefined at - // some point since TypeScript allows it. - return 'untitled'; - } -}; - /** * Given a throttle from a "security_solution" rule this will transform it into an "alerting" notifyWhen * on their saved object. diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.test.ts new file mode 100644 index 0000000000000..ba840ae6e580e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.test.ts @@ -0,0 +1,228 @@ +/* + * 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 { convertPatchAPIToInternalSchema, patchTypeSpecificSnakeToCamel } from './rule_converters'; +import { + getEqlRuleParams, + getMlRuleParams, + getQueryRuleParams, + getSavedQueryRuleParams, + getThreatRuleParams, + getThresholdRuleParams, +} from './rule_schemas.mock'; +import { getRuleMock } from '../routes/__mocks__/request_responses'; + +describe('rule_converters', () => { + describe('patchTypeSpecificSnakeToCamel', () => { + test('should accept EQL params when existing rule type is EQL', () => { + const patchParams = { + timestamp_field: 'event.created', + event_category_override: 'event.not_category', + tiebreaker_field: 'event.created', + }; + const rule = getEqlRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + timestampField: 'event.created', + eventCategoryOverride: 'event.not_category', + tiebreakerField: 'event.created', + }) + ); + }); + + test('should reject invalid EQL params when existing rule type is EQL', () => { + const patchParams = { + timestamp_field: 1, + event_category_override: 1, + tiebreaker_field: 1, + }; + const rule = getEqlRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'Invalid value "1" supplied to "timestamp_field",Invalid value "1" supplied to "event_category_override",Invalid value "1" supplied to "tiebreaker_field"' + ); + }); + + test('should accept threat match params when existing rule type is threat match', () => { + const patchParams = { + threat_indicator_path: 'my.indicator', + threat_query: 'test-query', + }; + const rule = getThreatRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + threatIndicatorPath: 'my.indicator', + threatQuery: 'test-query', + }) + ); + }); + + test('should reject invalid threat match params when existing rule type is threat match', () => { + const patchParams = { + threat_indicator_path: 1, + threat_query: 1, + }; + const rule = getThreatRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'Invalid value "1" supplied to "threat_query",Invalid value "1" supplied to "threat_indicator_path"' + ); + }); + + test('should accept query params when existing rule type is query', () => { + const patchParams = { + index: ['new-test-index'], + language: 'lucene', + }; + const rule = getQueryRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + index: ['new-test-index'], + language: 'lucene', + }) + ); + }); + + test('should reject invalid query params when existing rule type is query', () => { + const patchParams = { + index: [1], + language: 'non-language', + }; + const rule = getQueryRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'Invalid value "1" supplied to "index",Invalid value "non-language" supplied to "language"' + ); + }); + + test('should accept saved query params when existing rule type is saved query', () => { + const patchParams = { + index: ['new-test-index'], + language: 'lucene', + }; + const rule = getSavedQueryRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + index: ['new-test-index'], + language: 'lucene', + }) + ); + }); + + test('should reject invalid saved query params when existing rule type is saved query', () => { + const patchParams = { + index: [1], + language: 'non-language', + }; + const rule = getSavedQueryRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'Invalid value "1" supplied to "index",Invalid value "non-language" supplied to "language"' + ); + }); + + test('should accept threshold params when existing rule type is threshold', () => { + const patchParams = { + threshold: { + field: ['host.name'], + value: 107, + }, + }; + const rule = getThresholdRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + threshold: { + field: ['host.name'], + value: 107, + }, + }) + ); + }); + + test('should reject invalid threshold params when existing rule type is threshold', () => { + const patchParams = { + threshold: { + field: ['host.name'], + value: 'invalid', + }, + }; + const rule = getThresholdRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'Invalid value "invalid" supplied to "threshold,value"' + ); + }); + + test('should accept machine learning params when existing rule type is machine learning', () => { + const patchParams = { + anomaly_threshold: 5, + }; + const rule = getMlRuleParams(); + const patchedParams = patchTypeSpecificSnakeToCamel(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + anomalyThreshold: 5, + }) + ); + }); + + test('should reject invalid machine learning params when existing rule type is machine learning', () => { + const patchParams = { + anomaly_threshold: 'invalid', + }; + const rule = getMlRuleParams(); + expect(() => patchTypeSpecificSnakeToCamel(patchParams, rule)).toThrowError( + 'Invalid value "invalid" supplied to "anomaly_threshold"' + ); + }); + }); + + describe('convertPatchAPIToInternalSchema', () => { + test('should not update version for immutable rules', () => { + const patchParams = { + index: ['new-test-index'], + language: 'lucene', + }; + const rule = getRuleMock({ ...getQueryRuleParams(), immutable: true }); + const patchedParams = convertPatchAPIToInternalSchema(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ version: 1 }), + }) + ); + }); + + test('should update version for mutable rules', () => { + const patchParams = { + index: ['new-test-index'], + language: 'lucene', + }; + const rule = getRuleMock({ ...getQueryRuleParams() }); + const patchedParams = convertPatchAPIToInternalSchema(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ version: 2 }), + }) + ); + }); + + test('should not update version due to enabled, id, or rule_id, ', () => { + const patchParams = { + enabled: false, + id: 'some-id', + rule_id: 'some-rule-id', + }; + const rule = getRuleMock(getQueryRuleParams()); + const patchedParams = convertPatchAPIToInternalSchema(patchParams, rule); + expect(patchedParams).toEqual( + expect.objectContaining({ + params: expect.objectContaining({ version: 1 }), + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts index b615e705b556b..4fd92e87614f6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_converters.ts @@ -7,7 +7,9 @@ import uuid from 'uuid'; +import { BadRequestError } from '@kbn/securitysolution-es-utils'; import { ruleTypeMappings } from '@kbn/securitysolution-rules'; +import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { ResolvedSanitizedRule, SanitizedRule } from '@kbn/alerting-plugin/common'; import { @@ -19,17 +21,46 @@ import { RuleParams, TypeSpecificRuleParams, BaseRuleParams, + EqlRuleParams, + EqlSpecificRuleParams, + ThreatRuleParams, + ThreatSpecificRuleParams, + QueryRuleParams, + QuerySpecificRuleParams, + SavedQuerySpecificRuleParams, + SavedQueryRuleParams, + ThresholdRuleParams, + ThresholdSpecificRuleParams, + MachineLearningRuleParams, + MachineLearningSpecificRuleParams, + InternalRuleUpdate, } from './rule_schemas'; import { assertUnreachable } from '../../../../common/utility_types'; import { RuleExecutionSummary } from '../../../../common/detection_engine/schemas/common'; import { CreateRulesSchema, CreateTypeSpecific, + eqlPatchParams, + EqlPatchParams, FullResponseSchema, + machineLearningPatchParams, + MachineLearningPatchParams, + queryPatchParams, + QueryPatchParams, ResponseTypeSpecific, + savedQueryPatchParams, + SavedQueryPatchParams, + threatMatchPatchParams, + ThreatMatchPatchParams, + thresholdPatchParams, + ThresholdPatchParams, } from '../../../../common/detection_engine/schemas/request'; -import { AppClient } from '../../../types'; -import { DEFAULT_MAX_SIGNALS, SERVER_APP_ID } from '../../../../common/constants'; +import { PatchRulesSchema } from '../../../../common/detection_engine/schemas/request/patch_rules_schema'; +import { + DEFAULT_INDICATOR_SOURCE_PATH, + DEFAULT_MAX_SIGNALS, + SERVER_APP_ID, +} from '../../../../common/constants'; import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { transformFromAlertThrottle, @@ -78,7 +109,7 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif threatMapping: params.threat_mapping, threatLanguage: params.threat_language, threatIndex: params.threat_index, - threatIndicatorPath: params.threat_indicator_path, + threatIndicatorPath: params.threat_indicator_path ?? DEFAULT_INDICATOR_SOURCE_PATH, concurrentSearches: params.concurrent_searches, itemsPerSearch: params.items_per_search, }; @@ -130,9 +161,243 @@ export const typeSpecificSnakeToCamel = (params: CreateTypeSpecific): TypeSpecif } }; +const patchEqlParams = ( + params: EqlPatchParams, + existingRule: EqlRuleParams +): EqlSpecificRuleParams => { + return { + type: existingRule.type, + language: params.language ?? existingRule.language, + index: params.index ?? existingRule.index, + dataViewId: params.data_view_id ?? existingRule.dataViewId, + query: params.query ?? existingRule.query, + filters: params.filters ?? existingRule.filters, + timestampField: params.timestamp_field ?? existingRule.timestampField, + eventCategoryOverride: params.event_category_override ?? existingRule.eventCategoryOverride, + tiebreakerField: params.tiebreaker_field ?? existingRule.tiebreakerField, + }; +}; + +const patchThreatMatchParams = ( + params: ThreatMatchPatchParams, + existingRule: ThreatRuleParams +): ThreatSpecificRuleParams => { + return { + type: existingRule.type, + language: params.language ?? existingRule.language, + index: params.index ?? existingRule.index, + dataViewId: params.data_view_id ?? existingRule.dataViewId, + query: params.query ?? existingRule.query, + filters: params.filters ?? existingRule.filters, + savedId: params.saved_id ?? existingRule.savedId, + threatFilters: params.threat_filters ?? existingRule.threatFilters, + threatQuery: params.threat_query ?? existingRule.threatQuery, + threatMapping: params.threat_mapping ?? existingRule.threatMapping, + threatLanguage: params.threat_language ?? existingRule.threatLanguage, + threatIndex: params.threat_index ?? existingRule.threatIndex, + threatIndicatorPath: params.threat_indicator_path ?? existingRule.threatIndicatorPath, + concurrentSearches: params.concurrent_searches ?? existingRule.concurrentSearches, + itemsPerSearch: params.items_per_search ?? existingRule.itemsPerSearch, + }; +}; + +const patchQueryParams = ( + params: QueryPatchParams, + existingRule: QueryRuleParams +): QuerySpecificRuleParams => { + return { + type: existingRule.type, + language: params.language ?? existingRule.language, + index: params.index ?? existingRule.index, + dataViewId: params.data_view_id ?? existingRule.dataViewId, + query: params.query ?? existingRule.query, + filters: params.filters ?? existingRule.filters, + savedId: params.saved_id ?? existingRule.savedId, + }; +}; + +const patchSavedQueryParams = ( + params: SavedQueryPatchParams, + existingRule: SavedQueryRuleParams +): SavedQuerySpecificRuleParams => { + return { + type: existingRule.type, + language: params.language ?? existingRule.language, + index: params.index ?? existingRule.index, + dataViewId: params.data_view_id ?? existingRule.dataViewId, + query: params.query ?? existingRule.query, + filters: params.filters ?? existingRule.filters, + savedId: params.saved_id ?? existingRule.savedId, + }; +}; + +const patchThresholdParams = ( + params: ThresholdPatchParams, + existingRule: ThresholdRuleParams +): ThresholdSpecificRuleParams => { + return { + type: existingRule.type, + language: params.language ?? existingRule.language, + index: params.index ?? existingRule.index, + dataViewId: params.data_view_id ?? existingRule.dataViewId, + query: params.query ?? existingRule.query, + filters: params.filters ?? existingRule.filters, + savedId: params.saved_id ?? existingRule.savedId, + threshold: params.threshold + ? normalizeThresholdObject(params.threshold) + : existingRule.threshold, + }; +}; + +const patchMachineLearningParams = ( + params: MachineLearningPatchParams, + existingRule: MachineLearningRuleParams +): MachineLearningSpecificRuleParams => { + return { + type: existingRule.type, + anomalyThreshold: params.anomaly_threshold ?? existingRule.anomalyThreshold, + machineLearningJobId: params.machine_learning_job_id + ? normalizeMachineLearningJobIds(params.machine_learning_job_id) + : existingRule.machineLearningJobId, + }; +}; + +const parseValidationError = (error: string | null): BadRequestError => { + if (error != null) { + return new BadRequestError(error); + } else { + return new BadRequestError('unknown validation error'); + } +}; + +export const patchTypeSpecificSnakeToCamel = ( + params: PatchRulesSchema, + existingRule: RuleParams +): TypeSpecificRuleParams => { + // Here we do the validation of patch params by rule type to ensure that the fields that are + // passed in to patch are of the correct type, e.g. `query` is a string. Since the combined patch schema + // is a union of types where everything is optional, it's hard to do the validation before we know the rule type - + // a patch request that defines `event_category_override` as a number would not be assignable to the EQL patch schema, + // but would be assignable to the other rule types since they don't specify `event_category_override`. + switch (existingRule.type) { + case 'eql': { + const [validated, error] = validateNonExact(params, eqlPatchParams); + if (validated == null) { + throw parseValidationError(error); + } + return patchEqlParams(validated, existingRule); + } + case 'threat_match': { + const [validated, error] = validateNonExact(params, threatMatchPatchParams); + if (validated == null) { + throw parseValidationError(error); + } + return patchThreatMatchParams(validated, existingRule); + } + case 'query': { + const [validated, error] = validateNonExact(params, queryPatchParams); + if (validated == null) { + throw parseValidationError(error); + } + return patchQueryParams(validated, existingRule); + } + case 'saved_query': { + const [validated, error] = validateNonExact(params, savedQueryPatchParams); + if (validated == null) { + throw parseValidationError(error); + } + return patchSavedQueryParams(validated, existingRule); + } + case 'threshold': { + const [validated, error] = validateNonExact(params, thresholdPatchParams); + if (validated == null) { + throw parseValidationError(error); + } + return patchThresholdParams(validated, existingRule); + } + case 'machine_learning': { + const [validated, error] = validateNonExact(params, machineLearningPatchParams); + if (validated == null) { + throw parseValidationError(error); + } + return patchMachineLearningParams(validated, existingRule); + } + default: { + return assertUnreachable(existingRule); + } + } +}; + +const versionExcludedKeys = ['enabled', 'id', 'rule_id']; +const shouldUpdateVersion = (params: PatchRulesSchema): boolean => { + for (const key in params) { + if (!versionExcludedKeys.includes(key)) { + return true; + } + } + return false; +}; + +// eslint-disable-next-line complexity +export const convertPatchAPIToInternalSchema = ( + params: PatchRulesSchema, + existingRule: SanitizedRule<RuleParams> +): InternalRuleUpdate => { + const typeSpecificParams = patchTypeSpecificSnakeToCamel(params, existingRule.params); + const existingParams = existingRule.params; + return { + name: params.name ?? existingRule.name, + tags: params.tags ?? existingRule.tags, + params: { + author: params.author ?? existingParams.author, + buildingBlockType: params.building_block_type ?? existingParams.buildingBlockType, + description: params.description ?? existingParams.description, + ruleId: existingParams.ruleId, + falsePositives: params.false_positives ?? existingParams.falsePositives, + from: params.from ?? existingParams.from, + immutable: existingParams.immutable, + license: params.license ?? existingParams.license, + outputIndex: params.output_index ?? existingParams.outputIndex, + timelineId: params.timeline_id ?? existingParams.timelineId, + timelineTitle: params.timeline_title ?? existingParams.timelineTitle, + meta: params.meta ?? existingParams.meta, + maxSignals: params.max_signals ?? existingParams.maxSignals, + relatedIntegrations: existingParams.relatedIntegrations, + requiredFields: existingParams.requiredFields, + riskScore: params.risk_score ?? existingParams.riskScore, + riskScoreMapping: params.risk_score_mapping ?? existingParams.riskScoreMapping, + ruleNameOverride: params.rule_name_override ?? existingParams.ruleNameOverride, + setup: existingParams.setup, + severity: params.severity ?? existingParams.severity, + severityMapping: params.severity_mapping ?? existingParams.severityMapping, + threat: params.threat ?? existingParams.threat, + timestampOverride: params.timestamp_override ?? existingParams.timestampOverride, + to: params.to ?? existingParams.to, + references: params.references ?? existingParams.references, + namespace: params.namespace ?? existingParams.namespace, + note: params.note ?? existingParams.note, + // Always use the version from the request if specified. If it isn't specified, leave immutable rules alone and + // increment the version of mutable rules by 1. + version: + params.version ?? existingParams.immutable + ? existingParams.version + : shouldUpdateVersion(params) + ? existingParams.version + 1 + : existingParams.version, + exceptionsList: params.exceptions_list ?? existingParams.exceptionsList, + ...typeSpecificParams, + }, + schedule: { interval: params.interval ?? existingRule.schedule.interval }, + actions: params.actions ? params.actions.map(transformRuleToAlertAction) : existingRule.actions, + throttle: params.throttle ? transformToAlertThrottle(params.throttle) : existingRule.throttle, + notifyWhen: params.throttle ? transformToNotifyWhen(params.throttle) : existingRule.notifyWhen, + }; +}; + export const convertCreateAPIToInternalSchema = ( input: CreateRulesSchema, - siemClient: AppClient + immutable = false, + defaultEnabled = true ): InternalRuleCreate => { const typeSpecificParams = typeSpecificSnakeToCamel(input); const newRuleId = input.rule_id ?? uuid.v4(); @@ -148,9 +413,9 @@ export const convertCreateAPIToInternalSchema = ( ruleId: newRuleId, falsePositives: input.false_positives ?? [], from: input.from ?? 'now-6m', - immutable: false, + immutable, license: input.license, - outputIndex: input.output_index ?? siemClient.getSignalsIndex(), + outputIndex: input.output_index ?? '', timelineId: input.timeline_id, timelineTitle: input.timeline_title, meta: input.meta, @@ -174,7 +439,7 @@ export const convertCreateAPIToInternalSchema = ( ...typeSpecificParams, }, schedule: { interval: input.interval ?? '5m' }, - enabled: input.enabled ?? true, + enabled: input.enabled ?? defaultEnabled, actions: input.actions?.map(transformRuleToAlertAction) ?? [], throttle: transformToAlertThrottle(input.throttle), notifyWhen: transformToNotifyWhen(input.throttle), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts index 2580364329407..5aaebd5ad05e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.mock.ts @@ -15,6 +15,7 @@ import { MachineLearningRuleParams, QueryRuleParams, RuleParams, + SavedQueryRuleParams, ThreatRuleParams, ThresholdRuleParams, } from './rule_schemas'; @@ -125,6 +126,27 @@ export const getQueryRuleParams = (): QueryRuleParams => { }; }; +export const getSavedQueryRuleParams = (): SavedQueryRuleParams => { + return { + ...getBaseRuleParams(), + type: 'saved_query', + language: 'kuery', + query: 'user.name: root or user.name: admin', + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + dataViewId: undefined, + filters: [ + { + query: { + match_phrase: { + 'host.name': 'some-host', + }, + }, + }, + ], + savedId: 'some-id', + }; +}; + export const getThreatRuleParams = (): ThreatRuleParams => { return { ...getBaseRuleParams(), diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts index fa4d912368d33..4bc65a21f620b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/schemas/rule_schemas.ts @@ -131,6 +131,7 @@ const eqlSpecificRuleParams = t.type({ tiebreakerField: tiebreakerFieldOrUndefined, }); export const eqlRuleParams = t.intersection([baseRuleParams, eqlSpecificRuleParams]); +export type EqlSpecificRuleParams = t.TypeOf<typeof eqlSpecificRuleParams>; export type EqlRuleParams = t.TypeOf<typeof eqlRuleParams>; const threatSpecificRuleParams = t.type({ @@ -151,6 +152,7 @@ const threatSpecificRuleParams = t.type({ dataViewId: dataViewIdOrUndefined, }); export const threatRuleParams = t.intersection([baseRuleParams, threatSpecificRuleParams]); +export type ThreatSpecificRuleParams = t.TypeOf<typeof threatSpecificRuleParams>; export type ThreatRuleParams = t.TypeOf<typeof threatRuleParams>; const querySpecificRuleParams = t.exact( @@ -165,6 +167,7 @@ const querySpecificRuleParams = t.exact( }) ); export const queryRuleParams = t.intersection([baseRuleParams, querySpecificRuleParams]); +export type QuerySpecificRuleParams = t.TypeOf<typeof querySpecificRuleParams>; export type QueryRuleParams = t.TypeOf<typeof queryRuleParams>; const savedQuerySpecificRuleParams = t.type({ @@ -179,6 +182,7 @@ const savedQuerySpecificRuleParams = t.type({ savedId: saved_id, }); export const savedQueryRuleParams = t.intersection([baseRuleParams, savedQuerySpecificRuleParams]); +export type SavedQuerySpecificRuleParams = t.TypeOf<typeof savedQuerySpecificRuleParams>; export type SavedQueryRuleParams = t.TypeOf<typeof savedQueryRuleParams>; const thresholdSpecificRuleParams = t.type({ @@ -192,6 +196,7 @@ const thresholdSpecificRuleParams = t.type({ dataViewId: dataViewIdOrUndefined, }); export const thresholdRuleParams = t.intersection([baseRuleParams, thresholdSpecificRuleParams]); +export type ThresholdSpecificRuleParams = t.TypeOf<typeof thresholdSpecificRuleParams>; export type ThresholdRuleParams = t.TypeOf<typeof thresholdRuleParams>; const machineLearningSpecificRuleParams = t.type({ @@ -203,6 +208,7 @@ export const machineLearningRuleParams = t.intersection([ baseRuleParams, machineLearningSpecificRuleParams, ]); +export type MachineLearningSpecificRuleParams = t.TypeOf<typeof machineLearningSpecificRuleParams>; export type MachineLearningRuleParams = t.TypeOf<typeof machineLearningRuleParams>; export const typeSpecificRuleParams = t.union([ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts index 10f3b45ced94d..4b3f73a88d4af 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/types.ts @@ -5,106 +5,8 @@ * 2.0. */ -import { - From, - MachineLearningJobIdOrUndefined, - RiskScore, - RiskScoreMappingOrUndefined, - ThreatIndexOrUndefined, - ThreatQueryOrUndefined, - ThreatMappingOrUndefined, - ThreatLanguageOrUndefined, - ConcurrentSearchesOrUndefined, - ItemsPerSearchOrUndefined, - ThreatIndicatorPathOrUndefined, - ThreatsOrUndefined, - Type, - LanguageOrUndefined, - Severity, - SeverityMappingOrUndefined, - MaxSignals, -} from '@kbn/securitysolution-io-ts-alerting-types'; -import { Version } from '@kbn/securitysolution-io-ts-types'; - -import type { ListArrayOrUndefined } from '@kbn/securitysolution-io-ts-list-types'; import type { Filter } from '@kbn/es-query'; -import { RuleTypeParams as AlertingRuleTypeParams } from '@kbn/alerting-plugin/common'; -import { - AnomalyThresholdOrUndefined, - Description, - NoteOrUndefined, - ThresholdOrUndefined, - FalsePositives, - Immutable, - IndexOrUndefined, - OutputIndex, - QueryOrUndefined, - References, - SavedIdOrUndefined, - To, - TimelineIdOrUndefined, - TimelineTitleOrUndefined, - MetaOrUndefined, - RuleId, - AuthorOrUndefined, - BuildingBlockTypeOrUndefined, - LicenseOrUndefined, - RuleNameOverrideOrUndefined, - TimestampOverrideOrUndefined, - TimestampFieldOrUndefined, - EventCategoryOverrideOrUndefined, - TiebreakerFieldOrUndefined, -} from '../../../common/detection_engine/schemas/common/schemas'; export type PartialFilter = Partial<Filter>; -export interface RuleTypeParams extends AlertingRuleTypeParams { - anomalyThreshold?: AnomalyThresholdOrUndefined; - author: AuthorOrUndefined; - buildingBlockType: BuildingBlockTypeOrUndefined; - description: Description; - note: NoteOrUndefined; - timestampField?: TimestampFieldOrUndefined; - eventCategoryOverride?: EventCategoryOverrideOrUndefined; - tiebreakerField?: TiebreakerFieldOrUndefined; - falsePositives: FalsePositives; - from: From; - ruleId: RuleId; - immutable: Immutable; - index?: IndexOrUndefined; - language?: LanguageOrUndefined; - license: LicenseOrUndefined; - outputIndex: OutputIndex; - savedId?: SavedIdOrUndefined; - timelineId: TimelineIdOrUndefined; - timelineTitle: TimelineTitleOrUndefined; - meta: MetaOrUndefined; - machineLearningJobId?: MachineLearningJobIdOrUndefined; - query?: QueryOrUndefined; - filters?: unknown[]; - maxSignals: MaxSignals; - namespace?: string; - riskScore: RiskScore; - riskScoreMapping: RiskScoreMappingOrUndefined; - ruleNameOverride: RuleNameOverrideOrUndefined; - severity: Severity; - severityMapping: SeverityMappingOrUndefined; - threat: ThreatsOrUndefined; - threshold?: ThresholdOrUndefined; - threatFilters?: unknown[] | undefined; - threatIndex?: ThreatIndexOrUndefined; - threatIndicatorPath?: ThreatIndicatorPathOrUndefined; - threatQuery?: ThreatQueryOrUndefined; - threatMapping?: ThreatMappingOrUndefined; - threatLanguage?: ThreatLanguageOrUndefined; - timestampOverride: TimestampOverrideOrUndefined; - to: To; - type: Type; - references: References; - version: Version; - exceptionsList: ListArrayOrUndefined; - concurrentSearches?: ConcurrentSearchesOrUndefined; - itemsPerSearch?: ItemsPerSearchOrUndefined; -} - export type RefreshTypes = false | 'wait_for'; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts index a44f3628903cf..193bc524d84fa 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.test.ts @@ -11,7 +11,6 @@ import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; -import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { hostsKpiHosts } from './kpi/hosts'; import { hostsKpiUniqueIps } from './kpi/unique_ips'; @@ -19,7 +18,6 @@ import { hostsKpiUniqueIps } from './kpi/unique_ips'; jest.mock('./all'); jest.mock('./details'); jest.mock('./overview'); -jest.mock('./last_first_seen'); jest.mock('./uncommon_processes'); jest.mock('./kpi/hosts'); jest.mock('./kpi/unique_ips'); @@ -30,7 +28,6 @@ describe('hostsFactory', () => { [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, - [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts index 2549bf0279606..789eaaa77cf68 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/index.ts @@ -15,7 +15,6 @@ import { SecuritySolutionFactory } from '../types'; import { allHosts } from './all'; import { hostDetails } from './details'; import { hostOverview } from './overview'; -import { firstOrLastSeenHost } from './last_first_seen'; import { uncommonProcesses } from './uncommon_processes'; import { hostsKpiHosts } from './kpi/hosts'; import { hostsKpiUniqueIps } from './kpi/unique_ips'; @@ -27,7 +26,6 @@ export const hostsFactory: Record< [HostsQueries.details]: hostDetails, [HostsQueries.hosts]: allHosts, [HostsQueries.overview]: hostOverview, - [HostsQueries.firstOrLastSeen]: firstOrLastSeenHost, [HostsQueries.uncommonProcesses]: uncommonProcesses, [HostsKpiQueries.kpiHosts]: hostsKpiHosts, [HostsKpiQueries.kpiUniqueIps]: hostsKpiUniqueIps, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts index 22e887b7a6281..33e57ff62216b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/index.ts @@ -14,6 +14,7 @@ import { networkFactory } from './network'; import { ctiFactoryTypes } from './cti'; import { riskScoreFactory } from './risk_score'; import { usersFactory } from './users'; +import { firstLastSeenFactory } from './last_first_seen'; export const securitySolutionFactory: Record< FactoryQueryTypes, @@ -25,4 +26,5 @@ export const securitySolutionFactory: Record< ...networkFactory, ...ctiFactoryTypes, ...riskScoreFactory, + ...firstLastSeenFactory, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts similarity index 95% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts index 2d3c4b1b46890..851f610b1ed59 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/__mocks__/index.ts @@ -7,11 +7,11 @@ import { Direction, - HostFirstLastSeenRequestOptions, - HostsQueries, -} from '../../../../../../../common/search_strategy'; + FirstLastSeenRequestOptions, + FirstLastSeenQuery, +} from '../../../../../../common/search_strategy'; -export const mockOptions: HostFirstLastSeenRequestOptions = { +export const mockOptions: FirstLastSeenRequestOptions = { defaultIndex: [ 'apm-*-transaction*', 'traces-apm*', @@ -22,8 +22,9 @@ export const mockOptions: HostFirstLastSeenRequestOptions = { 'packetbeat-*', 'winlogbeat-*', ], - factoryQueryType: HostsQueries.firstOrLastSeen, - hostName: 'siem-kibana', + factoryQueryType: FirstLastSeenQuery, + field: 'host.name', + value: 'siem-kibana', order: Direction.asc, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts similarity index 57% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts index 2c1100fed0f9e..4e828bca6fde8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.test.ts @@ -5,8 +5,9 @@ * 2.0. */ -import * as buildQuery from './query.first_or_last_seen_host.dsl'; -import { firstOrLastSeenHost } from '.'; +import { Direction, FirstLastSeenRequestOptions } from '../../../../../common/search_strategy'; +import * as buildQuery from './query.first_or_last_seen.dsl'; +import { firstOrLastSeen } from '.'; import { mockOptions, mockSearchStrategyFirstSeenResponse, @@ -14,29 +15,25 @@ import { formattedSearchStrategyLastResponse, formattedSearchStrategyFirstResponse, } from './__mocks__'; -import { - Direction, - HostFirstLastSeenRequestOptions, -} from '../../../../../../common/search_strategy'; -describe('firstLastSeenHost search strategy', () => { +describe('firstLastSeen search strategy', () => { describe('first seen search strategy', () => { - const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenHostQuery'); + const buildFirstLastSeenQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenQuery'); afterEach(() => { - buildFirstLastSeenHostQuery.mockClear(); + buildFirstLastSeenQuery.mockClear(); }); describe('buildDsl', () => { test('should build dsl query', () => { - firstOrLastSeenHost.buildDsl(mockOptions); - expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(mockOptions); + firstOrLastSeen.buildDsl(mockOptions); + expect(buildFirstLastSeenQuery).toHaveBeenCalledWith(mockOptions); }); }); describe('parse', () => { test('should parse data correctly', async () => { - const result = await firstOrLastSeenHost.parse( + const result = await firstOrLastSeen.parse( mockOptions, mockSearchStrategyFirstSeenResponse ); @@ -46,23 +43,23 @@ describe('firstLastSeenHost search strategy', () => { }); describe('last seen search strategy', () => { - const buildFirstLastSeenHostQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenHostQuery'); + const buildFirstLastSeenQuery = jest.spyOn(buildQuery, 'buildFirstOrLastSeenQuery'); afterEach(() => { - buildFirstLastSeenHostQuery.mockClear(); + buildFirstLastSeenQuery.mockClear(); }); describe('buildDsl', () => { test('should build dsl query', () => { - const options: HostFirstLastSeenRequestOptions = { ...mockOptions, order: Direction.desc }; - firstOrLastSeenHost.buildDsl(options); - expect(buildFirstLastSeenHostQuery).toHaveBeenCalledWith(options); + const options: FirstLastSeenRequestOptions = { ...mockOptions, order: Direction.desc }; + firstOrLastSeen.buildDsl(options); + expect(buildFirstLastSeenQuery).toHaveBeenCalledWith(options); }); }); describe('parse', () => { test('should parse data correctly', async () => { - const result = await firstOrLastSeenHost.parse( + const result = await firstOrLastSeen.parse( { ...mockOptions, order: Direction.desc }, mockSearchStrategyLastSeenResponse ); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts similarity index 50% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts index 8794a95826a4b..6f9b21ec2dc2e 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/index.ts @@ -9,21 +9,22 @@ import { getOr } from 'lodash/fp'; import type { IEsSearchResponse } from '@kbn/data-plugin/common'; import { - HostFirstLastSeenStrategyResponse, - HostsQueries, - HostFirstLastSeenRequestOptions, -} from '../../../../../../common/search_strategy/security_solution/hosts'; + FactoryQueryTypes, + FirstLastSeenStrategyResponse, + FirstLastSeenQuery, + FirstLastSeenRequestOptions, +} from '../../../../../common/search_strategy/security_solution'; -import { inspectStringifyObject } from '../../../../../utils/build_query'; -import { SecuritySolutionFactory } from '../../types'; -import { buildFirstOrLastSeenHostQuery } from './query.first_or_last_seen_host.dsl'; +import { inspectStringifyObject } from '../../../../utils/build_query'; +import { SecuritySolutionFactory } from '../types'; +import { buildFirstOrLastSeenQuery } from './query.first_or_last_seen.dsl'; -export const firstOrLastSeenHost: SecuritySolutionFactory<HostsQueries.firstOrLastSeen> = { - buildDsl: (options: HostFirstLastSeenRequestOptions) => buildFirstOrLastSeenHostQuery(options), +export const firstOrLastSeen: SecuritySolutionFactory<typeof FirstLastSeenQuery> = { + buildDsl: (options: FirstLastSeenRequestOptions) => buildFirstOrLastSeenQuery(options), parse: async ( - options: HostFirstLastSeenRequestOptions, + options: FirstLastSeenRequestOptions, response: IEsSearchResponse<unknown> - ): Promise<HostFirstLastSeenStrategyResponse> => { + ): Promise<FirstLastSeenStrategyResponse> => { // First try to get the formatted field if it exists or not. const formattedField: string | null = getOr( null, @@ -32,7 +33,7 @@ export const firstOrLastSeenHost: SecuritySolutionFactory<HostsQueries.firstOrLa ); const inspect = { - dsl: [inspectStringifyObject(buildFirstOrLastSeenHostQuery(options))], + dsl: [inspectStringifyObject(buildFirstOrLastSeenQuery(options))], }; if (options.order === 'asc') { @@ -50,3 +51,10 @@ export const firstOrLastSeenHost: SecuritySolutionFactory<HostsQueries.firstOrLa } }, }; + +export const firstLastSeenFactory: Record< + typeof FirstLastSeenQuery, + SecuritySolutionFactory<FactoryQueryTypes> +> = { + [FirstLastSeenQuery]: firstOrLastSeen, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.test.ts similarity index 82% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.test.ts index fe2cb96144e22..9c808c99f3e42 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { buildFirstOrLastSeenHostQuery as buildQuery } from './query.first_or_last_seen_host.dsl'; +import { buildFirstOrLastSeenQuery as buildQuery } from './query.first_or_last_seen.dsl'; import { mockOptions, expectedDsl } from './__mocks__'; describe('buildQuery', () => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts similarity index 73% rename from x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts rename to x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts index 8a29ad747a200..60bdf50a214f4 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/last_first_seen/query.first_or_last_seen_host.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/last_first_seen/query.first_or_last_seen.dsl.ts @@ -5,14 +5,15 @@ * 2.0. */ -import { HostFirstLastSeenRequestOptions } from '../../../../../../common/search_strategy/security_solution/hosts'; +import { FirstLastSeenRequestOptions } from '../../../../../common/search_strategy/security_solution/first_last_seen'; -export const buildFirstOrLastSeenHostQuery = ({ - hostName, +export const buildFirstOrLastSeenQuery = ({ + field, + value, defaultIndex, order, -}: HostFirstLastSeenRequestOptions) => { - const filter = [{ term: { 'host.name': hostName } }]; +}: FirstLastSeenRequestOptions) => { + const filter = [{ term: { [field]: value } }]; const dslQuery = { allow_no_indices: true, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts index 6690730825f56..09f77776fb985 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/__mocks__/index.ts @@ -131,8 +131,6 @@ export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = { }, }, }, - lastSeen: { value: 1599703212208, value_as_string: '2020-09-10T02:00:12.208Z' }, - firstSeen: { value: 1598802015355, value_as_string: '2020-08-30T15:40:15.355Z' }, }, source: { meta: {}, @@ -189,8 +187,6 @@ export const mockSearchStrategyResponse: IEsSearchResponse<unknown> = { }, }, }, - lastSeen: { value: 1599703214494, value_as_string: '2020-09-10T02:00:14.494Z' }, - firstSeen: { value: 1598802015107, value_as_string: '2020-08-30T15:40:15.107Z' }, }, }, }, @@ -222,8 +218,6 @@ export const formattedSearchStrategyResponse = { source: { filter: { term: { 'source.ip': '35.196.65.164' } }, aggs: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, as: { filter: { exists: { field: 'source.as' } }, aggs: { @@ -267,8 +261,6 @@ export const formattedSearchStrategyResponse = { destination: { filter: { term: { 'destination.ip': '35.196.65.164' } }, aggs: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, as: { filter: { exists: { field: 'destination.as' } }, aggs: { @@ -341,8 +333,6 @@ export const formattedSearchStrategyResponse = { }, networkDetails: { source: { - firstSeen: '2020-08-30T15:40:15.107Z', - lastSeen: '2020-09-10T02:00:14.494Z', autonomousSystem: { number: [15169], organization: { name: ['Google LLC'] } }, geo: { continent_name: ['North America'], @@ -353,8 +343,6 @@ export const formattedSearchStrategyResponse = { }, }, destination: { - firstSeen: '2020-08-30T15:40:15.355Z', - lastSeen: '2020-09-10T02:00:12.208Z', autonomousSystem: { number: [15169], organization: { name: ['Google LLC'] } }, geo: { continent_name: ['North America'], @@ -403,8 +391,6 @@ export const expectedDsl = { source: { filter: { term: { 'source.ip': '35.196.65.164' } }, aggs: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, as: { filter: { exists: { field: 'source.as' } }, aggs: { @@ -448,8 +434,6 @@ export const expectedDsl = { destination: { filter: { term: { 'destination.ip': '35.196.65.164' } }, aggs: { - firstSeen: { min: { field: '@timestamp' } }, - lastSeen: { max: { field: '@timestamp' } }, as: { filter: { exists: { field: 'destination.as' } }, aggs: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts index 75938fadd8535..5fb4750fa8f28 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts @@ -17,9 +17,6 @@ import { } from '../../../../../../common/search_strategy/security_solution/network'; export const getNetworkDetailsAgg = (type: string, networkHit: NetworkHit | {}) => { - const firstSeen = getOr(null, `firstSeen.value_as_string`, networkHit); - const lastSeen = getOr(null, `lastSeen.value_as_string`, networkHit); - const autonomousSystem: AutonomousSystem | {} = getOr( {}, `${type}.as`, @@ -36,8 +33,6 @@ export const getNetworkDetailsAgg = (type: string, networkHit: NetworkHit | {}) return { [type]: { - firstSeen, - lastSeen, autonomousSystem: { ...autonomousSystem, }, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts index 1e153859b9bc5..9e56387939590 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/query.details_network.dsl.ts @@ -16,16 +16,6 @@ const getAggs = (type: string, ip: string) => { }, }, aggs: { - firstSeen: { - min: { - field: '@timestamp', - }, - }, - lastSeen: { - max: { - field: '@timestamp', - }, - }, as: { filter: { exists: { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap index c37a799b89463..2dcdb43bef47c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/index.test.tsx.snap @@ -13,16 +13,6 @@ Object { \\"track_total_hits\\": false, \\"body\\": { \\"aggregations\\": { - \\"first_seen\\": { - \\"min\\": { - \\"field\\": \\"@timestamp\\" - } - }, - \\"last_seen\\": { - \\"max\\": { - \\"field\\": \\"@timestamp\\" - } - }, \\"user_id\\": { \\"terms\\": { \\"field\\": \\"user.id\\", diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap index 08b234cb8370d..2cbfa200b3d6b 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/__snapshots__/query.user_details.dsl.test.ts.snap @@ -5,11 +5,6 @@ Object { "allow_no_indices": true, "body": Object { "aggregations": Object { - "first_seen": Object { - "min": Object { - "field": "@timestamp", - }, - }, "host_ip": Object { "aggs": Object { "timestamp": Object { @@ -61,11 +56,6 @@ Object { "size": 10, }, }, - "last_seen": Object { - "max": Object { - "field": "@timestamp", - }, - }, "user_domain": Object { "aggs": Object { "timestamp": Object { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts index 463e27d09356c..c8e015de5bdf9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/users/details/query.user_details.dsl.ts @@ -35,16 +35,6 @@ export const buildUserDetailsQuery = ({ track_total_hits: false, body: { aggregations: { - first_seen: { - min: { - field: '@timestamp', - }, - }, - last_seen: { - max: { - field: '@timestamp', - }, - }, ...buildFieldsTermAggregation(USER_FIELDS), }, query: { bool: { filter } }, diff --git a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts index 41e079a381f50..8b54738d2d05b 100644 --- a/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts +++ b/x-pack/plugins/security_solution/server/utils/build_validation/route_validation.ts @@ -38,6 +38,17 @@ export const buildRouteValidation = ) ); +export const buildRouteValidationNonExact = + <T extends rt.Mixed, A = rt.TypeOf<T>>(schema: T): RouteValidationFunction<A> => + (inputValue: unknown, validationResult: RouteValidationResultFactory) => + pipe( + schema.decode(inputValue), + fold<rt.Errors, A, RequestValidationResult<A>>( + (errors: rt.Errors) => validationResult.badRequest(formatErrors(errors).join()), + (validatedInput: A) => validationResult.ok(validatedInput) + ) + ); + export const buildRouteValidationWithExcess = < T extends rt.InterfaceType<rt.Props> | GenericIntersectionC | rt.PartialType<rt.Props>, diff --git a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts index 8c6504e478a08..a47beea25810f 100644 --- a/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts +++ b/x-pack/plugins/security_solution/server/utils/read_stream/create_stream_from_ndjson.ts @@ -9,7 +9,7 @@ import { Transform } from 'stream'; import { has, isString } from 'lodash/fp'; import { createMapStream, createFilterStream } from '@kbn/utils'; -import { ImportRulesSchemaDecoded } from '../../../common/detection_engine/schemas/request/import_rules_schema'; +import { ImportRulesSchema } from '../../../common/detection_engine/schemas/request/import_rules_schema'; export interface RulesObjectsExportResultDetails { /** number of successfully exported objects */ @@ -29,13 +29,13 @@ export const parseNdjsonStrings = (): Transform => { }; export const filterExportedCounts = (): Transform => { - return createFilterStream<ImportRulesSchemaDecoded | RulesObjectsExportResultDetails>( + return createFilterStream<ImportRulesSchema | RulesObjectsExportResultDetails>( (obj) => obj != null && !has('exported_count', obj) ); }; export const filterExceptions = (): Transform => { - return createFilterStream<ImportRulesSchemaDecoded | RulesObjectsExportResultDetails>( + return createFilterStream<ImportRulesSchema | RulesObjectsExportResultDetails>( (obj) => obj != null && !has('list_id', obj) ); }; diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index b80be0b690135..b59001b1fdfff 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -167,7 +167,7 @@ export interface Process { orphans: Process[]; // currently, orphans are rendered inline with the entry session leaders children parent: Process | undefined; autoExpand: boolean; - searchMatched: string | null; // either false, or set to searchQuery + searchMatched: number[] | null; // either false, or set to searchQuery addEvent(event: ProcessEvent): void; addAlert(alert: ProcessEvent): void; addChild(child: Process): void; diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts index 9a0834a015476..5ec8568a013b9 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts @@ -5,6 +5,7 @@ * 2.0. */ import uuid from 'uuid'; +import { escapeRegExp } from 'lodash'; import { sortProcesses } from '../../../common/utils/sort_processes'; import { AlertStatusEventEntityIdMap, @@ -210,7 +211,10 @@ export const searchProcessTree = ( // for now plain text search is limited to searching process.working_directory + process.args const text = `${workingDirectory ?? ''} ${args?.join(' ')}`; - process.searchMatched = text.includes(searchQuery) ? searchQuery : null; + const searchMatch = [...text.matchAll(new RegExp(escapeRegExp(searchQuery), 'gi'))]; + + process.searchMatched = + searchMatch.length > 0 ? getSearchMatchedIndices(text, searchMatch) : null; if (process.searchMatched) { results.push(process); @@ -223,6 +227,21 @@ export const searchProcessTree = ( return results.sort(sortProcesses); }; +const getSearchMatchedIndices = (text: string, matches: RegExpMatchArray[]) => { + return text.split('').reduce((accum, _, idx) => { + const findMatch = matches.find( + (match) => + match.index !== undefined && idx >= match.index && idx < match.index + match[0].length + ); + + if (findMatch) { + accum = [...accum, idx]; + } + + return accum; + }, [] as number[]); +}; + // Iterate over all processes in processMap, and mark each process (and it's ancestors) for auto expansion if: // a) the process was "user entered" (aka an interactive group leader) // b) we are jumping to a specific process diff --git a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts index c742c7cd59569..3b33db65f8274 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts @@ -41,7 +41,7 @@ export class ProcessImpl implements Process { children: Process[]; parent: Process | undefined; autoExpand: boolean; - searchMatched: string | null; + searchMatched: number[] | null; orphans: Process[]; constructor(id: string) { diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx index 2ba29107661c5..0f0d1daffe36b 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx @@ -111,7 +111,7 @@ describe('ProcessTree component', () => { it('When Verbose mode is ON, it should show all childrens', () => { renderResult = mockedContext.render(<ProcessTree {...props} verboseMode={true} />); - expect(renderResult.queryByText('cat')).toBeTruthy(); + expect(renderResult.queryByRole('document', { name: '/home/vagrant cat' })).toBeTruthy(); }); }); }); diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx index cff05c5c1003b..857c791d81a3a 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx @@ -294,14 +294,23 @@ describe('ProcessTreeNode component', () => { }); describe('Search', () => { it('highlights text within the process node line item if it matches the searchQuery', () => { + const searchQuery = '/vagr'; // set a mock search matched indicator for the process (typically done by ProcessTree/helpers.ts) - processMock.searchMatched = '/vagr'; + const processMockClone = { ...processMock, searchMatched: [5, 6, 7, 8, 9] }; - renderResult = mockedContext.render(<ProcessTreeNode {...props} />); + renderResult = mockedContext.render( + <ProcessTreeNode {...props} process={processMockClone} /> + ); + expect(renderResult.queryAllByTestId(`sessionView:splitTextIsHighlighted`)).toHaveLength( + searchQuery.length + ); expect( - renderResult.getByTestId('sessionView:processNodeSearchHighlight').textContent - ).toEqual('/vagr'); + renderResult + .queryAllByTestId(`sessionView:splitTextIsHighlighted`) + .map(({ textContent }) => textContent) + .join('') + ).toEqual(searchQuery); // ensures we are showing the rest of the info, and not replacing it with just the match. const { process } = props.process.getDetails(); diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx index f65cb0f25530a..47f56b6c51a42 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx @@ -12,8 +12,6 @@ * 2.0. */ import React, { - useRef, - useLayoutEffect, useState, useEffect, MouseEvent, @@ -35,6 +33,7 @@ import { useStyles } from './styles'; import { SplitText } from './split_text'; import { Nbsp } from './nbsp'; import { useDateFormat } from '../../hooks'; +import { TextHighlight } from './text_highlight'; export interface ProcessDeps { process: Process; @@ -74,14 +73,16 @@ export function ProcessTreeNode({ loadPreviousButton, loadNextButton, }: ProcessDeps) { - const textRef = useRef<HTMLSpanElement>(null); - const [childrenExpanded, setChildrenExpanded] = useState(isSessionLeader || process.autoExpand); const [alertsExpanded, setAlertsExpanded] = useState(false); const { searchMatched } = process; const dateFormat = useDateFormat(); + useEffect(() => { + setChildrenExpanded(process.autoExpand); + }, [process.autoExpand]); + // forces nodes to expand if the selected process is a descendant useEffect(() => { if (!childrenExpanded && selectedProcess) { @@ -91,10 +92,6 @@ export function ProcessTreeNode({ } }, [selectedProcess, process, childrenExpanded]); - useEffect(() => { - setChildrenExpanded(process.autoExpand); - }, [process.autoExpand]); - const alerts = process.getAlerts(); const hasAlerts = useMemo(() => !!alerts.length, [alerts]); const hasInvestigatedAlert = useMemo( @@ -135,22 +132,6 @@ export function ProcessTreeNode({ } }, [hasInvestigatedAlert]); - useLayoutEffect(() => { - if (searchMatched !== null && textRef.current) { - const regex = new RegExp(searchMatched); - const text = textRef.current.textContent; - - if (text) { - const html = text.replace(regex, (match) => { - return `<span data-test-subj="sessionView:processNodeSearchHighlight" style="${styles.searchHighlight}">${match}</span>`; - }); - - // eslint-disable-next-line no-unsanitized/property - textRef.current.innerHTML = '<span>' + html + '</span>'; - } - } - }, [searchMatched, styles.searchHighlight]); - const onChildrenToggle = useCallback(() => { setChildrenExpanded(!childrenExpanded); }, [childrenExpanded]); @@ -272,12 +253,20 @@ export function ProcessTreeNode({ <EuiToolTip position="top" content={iconTooltip}> <EuiIcon data-test-subj={iconTestSubj} type={processIcon} css={styles.icon} /> </EuiToolTip> - <span ref={textRef} css={styles.textSection}> - <span css={styles.workingDir}>{dataOrDash(workingDirectory)}</span> - <Nbsp /> - <span css={styles.darkText}>{dataOrDash(args?.[0])}</span> - <Nbsp /> - <SplitText>{args?.slice(1).join(' ') || ''}</SplitText> + <span css={styles.textSection}> + <TextHighlight + text={`${workingDirectory ?? ''} ${args?.join(' ')}`} + match={process.searchMatched} + highlightStyle={styles.searchHighlight} + > + <SplitText css={styles.workingDir}> + {dataOrDash(workingDirectory) + ' '} + </SplitText> + <SplitText css={styles.darkText}>{`${dataOrDash(args?.[0])}`}</SplitText> + <SplitText> + {args && args.length > 1 ? ' ' + args?.slice(1).join(' ') : ''} + </SplitText> + </TextHighlight> </span> </> )} diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/split_text.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/split_text.test.tsx index eaa18b18d81cf..88aaf6f2fff0f 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/split_text.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/split_text.test.tsx @@ -19,10 +19,4 @@ describe('SplitText component', () => { } expect(renderResult.container.textContent?.replace(/\s+/g, ' ')).toEqual(text); }); - it('should provide an acessible label for screen readers', async () => { - const text = 'hello world'; - - const renderResult = render(<SplitText>{text}</SplitText>); - expect(renderResult.queryByRole('document', { name: text })).toBeTruthy(); - }); }); diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/split_text.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/split_text.tsx index d31590c9ab967..6ac7a12aa0f4e 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/split_text.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/split_text.tsx @@ -10,30 +10,40 @@ import { CSSObject } from '@emotion/react'; type Props = { children: string; + highlightIndices?: number[]; + highlightStyle?: CSSObject; role?: string; }; -const css: CSSObject = { - '&&': { - display: 'inline', - fontSize: 0, - lineHeight: 0, - }, -}; - // Split a text into multiple spans, each of which a single character. This is // useful for creating inline "like" text but still having control over the blocks // exclusive features, such height or line-height. -// It adds a `aria-label` attribute to a parent span, which is used by screen readers to -// read the text as a single block. -export const SplitText = ({ children, role = 'document', ...props }: Props) => ( - <span css={css} aria-label={children} role={role}> - {children.split('').map(function (char, index) { - return ( - <span aria-hidden="true" key={index} {...props}> - {char === ' ' ? <> </> : char} - </span> - ); - })} - </span> -); + +const css: CSSObject = {}; + +export const SplitText = ({ + children, + role = 'document', + highlightIndices, + highlightStyle, + ...props +}: Props) => { + return ( + <> + {children.split('').map(function (char, index) { + const isHighlighted = highlightIndices?.includes(index); + return ( + <span + aria-hidden="true" + css={isHighlighted ? highlightStyle : css} + key={index} + {...(isHighlighted ? { 'data-test-subj': `sessionView:splitTextIsHighlighted` } : {})} + {...props} + > + {char === ' ' ? <> </> : char} + </span> + ); + })} + </> + ); +}; diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts b/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts index 54dbdb1bc4565..3936fc35aac81 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts +++ b/x-pack/plugins/session_view/public/components/process_tree_node/styles.ts @@ -109,6 +109,9 @@ export const useStyles = ({ backgroundColor: bgColor, width: `calc(100% + ${depth} * ${TREE_INDENT} + ${PROCESS_TREE_LEFT_PADDING})`, }, + '.euiToolTipAnchor': { + verticalAlign: 'middle', + }, }; const textSection: CSSObject = { diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/text_highlight.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/text_highlight.test.tsx new file mode 100644 index 0000000000000..733327ab23e57 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/process_tree_node/text_highlight.test.tsx @@ -0,0 +1,26 @@ +/* + * 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 { TextHighlight } from './text_highlight'; + +describe('TextHighlight component', () => { + it('should provide an acessible label for screen readers', async () => { + const text = 'hello world'; + + const renderResult = render( + <TextHighlight text={text} match={[0, 1]} highlightStyle={{}}> + <> + <span>{text}</span> + </> + </TextHighlight> + ); + + expect(renderResult.queryByRole('document', { name: text })).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/text_highlight.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/text_highlight.tsx new file mode 100644 index 0000000000000..6d21726e0d430 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/process_tree_node/text_highlight.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { CSSObject } from '@emotion/react'; + +type Props = { + children: JSX.Element | JSX.Element[]; + text: string; + match: null | number[]; + highlightStyle: any; +}; + +const css: CSSObject = { + '&&': { + display: 'inline', + fontSize: 0, + lineHeight: 0, + verticalAlign: 'middle', + }, +}; +// Component that takes an array of matching indices in a text and pass down a highlight +// css style prop across its children +// Currently it works only for a single child level, designed to be in combination with SplitText +// It adds a `aria-label` attribute to a parent span, which is used by screen readers to +// read the text as a single block. +export const TextHighlight = ({ children, match, text, highlightStyle }: Props): JSX.Element => { + let startIdx = 0; + + return ( + <span css={css} aria-label={text} role="document"> + {!match + ? children + : React.Children.map(children, (child) => { + const childText = child.props.children; + const childTextLength = childText.length; + + const highlightIndices = match.map((v) => v - startIdx); + startIdx += childTextLength; + + return React.cloneElement(child, { + highlightIndices, + highlightStyle, + }); + })} + </span> + ); +}; diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index f780ee704e319..0d40b03f22081 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -8,7 +8,7 @@ import Boom from '@hapi/boom'; // @ts-ignore -import type { CoreSetup, IBasePath, IRouter } from '@kbn/core/server'; +import type { CoreSetup, IBasePath, IRouter, RequestHandlerContext } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; import * as kbnTestServer from '@kbn/core/test_helpers/kbn_server'; @@ -153,7 +153,7 @@ describe.skip('onPostAuthInterceptor', () => { getSpacesService: () => spacesServiceStart, }); - const router = http.createRouter('/'); + const router = http.createRouter<RequestHandlerContext>('/'); initKbnServer(router, http.basePath); diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts index 8e3eb5a555212..7dd66381b4be3 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_request_interceptor.test.ts @@ -12,6 +12,7 @@ import type { IRouter, KibanaRequest, KibanaResponseFactory, + RequestHandlerContext, } from '@kbn/core/server'; import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import * as kbnTestServer from '@kbn/core/test_helpers/kbn_server'; @@ -89,7 +90,7 @@ describe.skip('onRequestInterceptor', () => { http: http as unknown as CoreSetup['http'], }); - const router = http.createRouter('/'); + const router = http.createRouter<RequestHandlerContext>('/'); initKbnServer(router, http.basePath); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.test.tsx index 94e6a6b0c0cd4..75bbf0afbc77d 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.test.tsx @@ -7,15 +7,15 @@ import React from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; -import { DataViewSelectPopover } from './data_view_select_popover'; +import { DataViewSelectPopover, DataViewSelectPopoverProps } from './data_view_select_popover'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { act } from 'react-dom/test-utils'; -const props = { +const props: DataViewSelectPopoverProps = { onSelectDataView: () => {}, - initialDataViewTitle: 'kibana_sample_data_logs', - initialDataViewId: 'mock-data-logs-id', + dataViewName: 'kibana_sample_data_logs', + dataViewId: 'mock-data-logs-id', }; const dataViewOptions = [ diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx index a62b640e0d8eb..fad3230e57f1e 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/data_view_select_popover.tsx @@ -5,48 +5,81 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { + EuiButtonEmpty, EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiPopover, + EuiPopoverFooter, EuiPopoverTitle, + EuiText, + useEuiPaddingCSS, } from '@elastic/eui'; import { DataViewsList } from '@kbn/unified-search-plugin/public'; import { DataViewListItem } from '@kbn/data-views-plugin/public'; import { useTriggersAndActionsUiDeps } from '../es_query/util'; -interface DataViewSelectPopoverProps { +export interface DataViewSelectPopoverProps { onSelectDataView: (newDataViewId: string) => void; - initialDataViewTitle: string; - initialDataViewId?: string; + dataViewName?: string; + dataViewId?: string; } export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopoverProps> = ({ onSelectDataView, - initialDataViewTitle, - initialDataViewId, + dataViewName, + dataViewId, }) => { - const { data } = useTriggersAndActionsUiDeps(); + const { data, dataViewEditor } = useTriggersAndActionsUiDeps(); const [dataViewItems, setDataViewsItems] = useState<DataViewListItem[]>(); const [dataViewPopoverOpen, setDataViewPopoverOpen] = useState(false); - const [selectedDataViewId, setSelectedDataViewId] = useState(initialDataViewId); - const [selectedTitle, setSelectedTitle] = useState<string>(initialDataViewTitle); + const closeDataViewEditor = useRef<() => void | undefined>(); + + const loadDataViews = useCallback(async () => { + const fetchedDataViewItems = await data.dataViews.getIdsWithTitle(); + setDataViewsItems(fetchedDataViewItems); + }, [setDataViewsItems, data.dataViews]); + + const closeDataViewPopover = useCallback(() => setDataViewPopoverOpen(false), []); + + const createDataView = useMemo( + () => + dataViewEditor?.userPermissions.editDataView() + ? () => { + closeDataViewEditor.current = dataViewEditor.openEditor({ + onSave: async (createdDataView) => { + if (createdDataView.id) { + await onSelectDataView(createdDataView.id); + await loadDataViews(); + } + }, + }); + } + : undefined, + [dataViewEditor, onSelectDataView, loadDataViews] + ); useEffect(() => { - const initDataViews = async () => { - const fetchedDataViewItems = await data.dataViews.getIdsWithTitle(); - setDataViewsItems(fetchedDataViewItems); + return () => { + // Make sure to close the editor when unmounting + if (closeDataViewEditor.current) { + closeDataViewEditor.current(); + } }; - initDataViews(); - }, [data.dataViews]); + }, []); - const closeDataViewPopover = useCallback(() => setDataViewPopoverOpen(false), []); + useEffect(() => { + loadDataViews(); + }, [loadDataViews]); + + const createDataViewButtonPadding = useEuiPaddingCSS('left'); if (!dataViewItems) { return null; @@ -62,12 +95,17 @@ export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopove description={i18n.translate('xpack.stackAlerts.components.ui.alertParams.dataViewLabel', { defaultMessage: 'data view', })} - value={selectedTitle} + value={ + dataViewName ?? + i18n.translate('xpack.stackAlerts.components.ui.alertParams.dataViewPlaceholder', { + defaultMessage: 'Select a data view', + }) + } isActive={dataViewPopoverOpen} onClick={() => { setDataViewPopoverOpen(true); }} - isInvalid={!selectedTitle} + isInvalid={!dataViewId} /> } isOpen={dataViewPopoverOpen} @@ -98,22 +136,53 @@ export const DataViewSelectPopover: React.FunctionComponent<DataViewSelectPopove </EuiFlexItem> </EuiFlexGroup> </EuiPopoverTitle> - <EuiFormRow id="indexSelectSearchBox" fullWidth> + <EuiFormRow + id="indexSelectSearchBox" + fullWidth + css={` + .euiPanel { + padding: 0; + } + `} + > <DataViewsList dataViewsList={dataViewItems} onChangeDataView={(newId) => { - setSelectedDataViewId(newId); - const newTitle = dataViewItems?.find(({ id }) => id === newId)?.title; - if (newTitle) { - setSelectedTitle(newTitle); - } - onSelectDataView(newId); closeDataViewPopover(); }} - currentDataViewId={selectedDataViewId} + currentDataViewId={dataViewId} /> </EuiFormRow> + {createDataView ? ( + <EuiPopoverFooter paddingSize="none"> + <EuiButtonEmpty + css={createDataViewButtonPadding.s} + iconType="plusInCircleFilled" + data-test-subj="chooseDataViewPopover.createDataViewButton" + onClick={() => { + closeDataViewPopover(); + createDataView(); + }} + > + {i18n.translate( + 'xpack.stackAlerts.components.ui.alertParams.dataViewPopover.createDataViewButton', + { + defaultMessage: 'Create a data view', + } + )} + </EuiButtonEmpty> + </EuiPopoverFooter> + ) : ( + <EuiPopoverFooter> + <EuiText color="subdued" size="xs"> + <FormattedMessage + id="xpack.stackAlerts.components.ui.alertParams.dataViewPopover.createDataViewButton.noPermissionDescription" + defaultMessage="You need additional privileges to create data views. Contact your administrator." + /> + </EuiText> + </EuiPopoverFooter> + )} </div> </EuiPopover> ); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.tsx index aad203016a065..c3923e08bbd78 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.tsx @@ -104,7 +104,13 @@ export const IndexSelectPopover: React.FunctionComponent<Props> = ({ description={i18n.translate('xpack.stackAlerts.components.ui.alertParams.indexLabel', { defaultMessage: 'index', })} - value={index && index.length > 0 ? renderIndices(index) : firstFieldOption.text} + value={ + index && index.length > 0 + ? renderIndices(index) + : i18n.translate('xpack.stackAlerts.components.ui.alertParams.indexPlaceholder', { + defaultMessage: 'Select an index', + }) + } isActive={indexPopoverOpen} onClick={() => { setIndexPopoverOpen(true); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts index da85c878f3281..91b48d8b38c4c 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/constants.ts @@ -31,6 +31,7 @@ export const EXPRESSION_ERRORS = { thresholdComparator: new Array<string>(), timeWindowSize: new Array<string>(), searchConfiguration: new Array<string>(), + searchType: new Array<string>(), }; export const EXPRESSION_ERROR_KEYS = Object.keys(EXPRESSION_ERRORS) as ErrorKey[]; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx index 97bf42ca2599a..6432ba9a324a8 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/es_query_expression.tsx @@ -13,34 +13,20 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { XJsonMode } from '@kbn/ace'; import 'brace/theme/github'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiFormRow, - EuiTitle, - EuiLink, - EuiIconTip, -} from '@elastic/eui'; +import { EuiFormRow, EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui'; import { DocLinksStart, HttpSetup } from '@kbn/core/public'; -import { XJson, EuiCodeEditor } from '@kbn/es-ui-shared-plugin/public'; +import { EuiCodeEditor, XJson } from '@kbn/es-ui-shared-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { - getFields, - ValueExpression, - RuleTypeParamsExpressionProps, - ForLastExpression, - ThresholdExpression, -} from '@kbn/triggers-actions-ui-plugin/public'; +import { getFields, RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { parseDuration } from '@kbn/alerting-plugin/common'; -import { validateExpression } from '../validation'; +import { hasExpressionValidationErrors } from '../validation'; import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_query'; import { EsQueryAlertParams, SearchType } from '../types'; import { IndexSelectPopover } from '../../components/index_select_popover'; import { DEFAULT_VALUES } from '../constants'; -import { TestQueryRow } from './test_query_row'; -import { totalHitsToNumber } from './use_test_query'; +import { RuleCommonExpressions } from '../rule_common_expressions'; +import { totalHitsToNumber } from '../test_query_row'; const { useXJsonMode } = XJson; const xJsonMode = new XJsonMode(); @@ -50,13 +36,9 @@ interface KibanaDeps { docLinks: DocLinksStart; } -export const EsQueryExpression = ({ - ruleParams, - setRuleParams, - setRuleProperty, - errors, - data, -}: RuleTypeParamsExpressionProps<EsQueryAlertParams<SearchType.esQuery>>) => { +export const EsQueryExpression: React.FC< + RuleTypeParamsExpressionProps<EsQueryAlertParams<SearchType.esQuery>> +> = ({ ruleParams, setRuleParams, setRuleProperty, errors, data }) => { const { index, timeField, @@ -68,7 +50,7 @@ export const EsQueryExpression = ({ timeWindowUnit, } = ruleParams; - const [currentAlertParams, setCurrentAlertParams] = useState< + const [currentRuleParams, setCurrentRuleParams] = useState< EsQueryAlertParams<SearchType.esQuery> >({ ...ruleParams, @@ -78,12 +60,12 @@ export const EsQueryExpression = ({ thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, size: size ?? DEFAULT_VALUES.SIZE, esQuery: esQuery ?? DEFAULT_VALUES.QUERY, - searchType: 'esQuery', + searchType: SearchType.esQuery, }); const setParam = useCallback( (paramField: string, paramValue: unknown) => { - setCurrentAlertParams((currentParams) => ({ + setCurrentRuleParams((currentParams) => ({ ...currentParams, [paramField]: paramValue, })); @@ -106,7 +88,7 @@ export const EsQueryExpression = ({ const { convertToJson, setXJson, xJson } = useXJsonMode(DEFAULT_VALUES.QUERY); const setDefaultExpressionValues = async () => { - setRuleProperty('params', currentAlertParams); + setRuleProperty('params', currentRuleParams); setXJson(esQuery ?? DEFAULT_VALUES.QUERY); if (index && index.length > 0) { @@ -127,11 +109,8 @@ export const EsQueryExpression = ({ }; const hasValidationErrors = useCallback(() => { - const { errors: validationErrors } = validateExpression(currentAlertParams); - return Object.keys(validationErrors).some( - (key) => validationErrors[key] && validationErrors[key].length - ); - }, [currentAlertParams]); + return hasExpressionValidationErrors(currentRuleParams); + }, [currentRuleParams]); const onTestQuery = useCallback(async () => { const window = `${timeWindowSize}${timeWindowUnit}`; @@ -165,12 +144,14 @@ export const EsQueryExpression = ({ <EuiTitle size="xs"> <h5> <FormattedMessage - id="xpack.stackAlerts.esQuery.ui.selectIndex" - defaultMessage="Select an index and size" + id="xpack.stackAlerts.esQuery.ui.selectIndexPrompt" + defaultMessage="Select an index and time field" /> </h5> </EuiTitle> + <EuiSpacer size="s" /> + <IndexSelectPopover index={index} data-test-subj="indexSelectPopover" @@ -199,25 +180,14 @@ export const EsQueryExpression = ({ }} onTimeFieldChange={(updatedTimeField: string) => setParam('timeField', updatedTimeField)} /> - <ValueExpression - description={i18n.translate('xpack.stackAlerts.esQuery.ui.sizeExpression', { - defaultMessage: 'Size', - })} - data-test-subj="sizeValueExpression" - value={size} - errors={errors.size} - display="fullWidth" - popupPosition={'upLeft'} - onChangeSelectedValue={(updatedValue) => { - setParam('size', updatedValue); - }} - /> - <EuiSpacer /> + + <EuiSpacer size="s" /> + <EuiTitle size="xs"> <h5> <FormattedMessage - id="xpack.stackAlerts.esQuery.ui.queryPrompt" - defaultMessage="Define the Elasticsearch query" + id="xpack.stackAlerts.esQuery.ui.defineQueryPrompt" + defaultMessage="Define your query using Query DSL" /> </h5> </EuiTitle> @@ -225,12 +195,6 @@ export const EsQueryExpression = ({ <EuiFormRow id="queryEditor" fullWidth - label={ - <FormattedMessage - id="xpack.stackAlerts.esQuery.ui.queryPrompt.label" - defaultMessage="Elasticsearch query" - /> - } isInvalid={errors.esQuery.length > 0} error={errors.esQuery} helpText={ @@ -258,62 +222,33 @@ export const EsQueryExpression = ({ }} /> </EuiFormRow> - <TestQueryRow fetch={onTestQuery} hasValidationErrors={hasValidationErrors()} /> - <EuiSpacer /> - <EuiFlexGroup alignItems="center" responsive={false} gutterSize="none"> - <EuiFlexItem grow={false}> - <EuiTitle size="xs"> - <h5> - <FormattedMessage - id="xpack.stackAlerts.esQuery.ui.conditionPrompt" - defaultMessage="When number of matches" - /> - </h5> - </EuiTitle> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiIconTip - position="right" - color="subdued" - type="questionInCircle" - iconProps={{ - className: 'eui-alignTop', - }} - content={i18n.translate('xpack.stackAlerts.esQuery.ui.conditionPrompt.toolTip', { - defaultMessage: 'The time window defined below applies only to the first rule check.', - })} - /> - </EuiFlexItem> - </EuiFlexGroup> + <EuiSpacer size="s" /> - <ThresholdExpression - data-test-subj="thresholdExpression" - thresholdComparator={thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR} + + <RuleCommonExpressions threshold={threshold ?? DEFAULT_VALUES.THRESHOLD} - errors={errors} - display="fullWidth" - popupPosition={'upLeft'} - onChangeSelectedThreshold={(selectedThresholds) => - setParam('threshold', selectedThresholds) - } - onChangeSelectedThresholdComparator={(selectedThresholdComparator) => - setParam('thresholdComparator', selectedThresholdComparator) - } - /> - <ForLastExpression - data-test-subj="forLastExpression" - popupPosition={'upLeft'} + thresholdComparator={thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR} timeWindowSize={timeWindowSize} timeWindowUnit={timeWindowUnit} - display="fullWidth" - errors={errors} + size={size} + onChangeThreshold={(selectedThresholds) => setParam('threshold', selectedThresholds)} + onChangeThresholdComparator={(selectedThresholdComparator) => + setParam('thresholdComparator', selectedThresholdComparator) + } onChangeWindowSize={(selectedWindowSize: number | undefined) => setParam('timeWindowSize', selectedWindowSize) } onChangeWindowUnit={(selectedWindowUnit: string) => setParam('timeWindowUnit', selectedWindowUnit) } + onChangeSizeValue={(updatedValue) => { + setParam('size', updatedValue); + }} + errors={errors} + hasValidationErrors={hasValidationErrors()} + onTestFetch={onTestQuery} /> + <EuiSpacer /> </Fragment> ); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.test.tsx new file mode 100644 index 0000000000000..e26bfb48ff8b5 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.test.tsx @@ -0,0 +1,241 @@ +/* + * 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 { mountWithIntl } from '@kbn/test-jest-helpers'; +import 'brace'; +import React, { useState } from 'react'; +import { docLinksServiceMock } from '@kbn/core/public/mocks'; +import { httpServiceMock } from '@kbn/core/public/mocks'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; +import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; +import { CommonAlertParams, EsQueryAlertParams, SearchType } from '../types'; +import { EsQueryAlertTypeExpression } from './expression'; +import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { Subject } from 'rxjs'; +import { ISearchSource } from '@kbn/data-plugin/common'; +import { IUiSettingsClient } from '@kbn/core/public'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { act } from 'react-dom/test-utils'; +import { ReactWrapper } from 'enzyme'; + +const defaultEsQueryRuleParams: EsQueryAlertParams<SearchType.esQuery> = { + size: 100, + thresholdComparator: '>', + threshold: [0], + timeWindowSize: 15, + timeWindowUnit: 's', + index: ['test-index'], + timeField: '@timestamp', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + searchType: SearchType.esQuery, +}; +const defaultSearchSourceRuleParams: EsQueryAlertParams<SearchType.searchSource> = { + size: 100, + thresholdComparator: '>', + threshold: [0], + timeWindowSize: 15, + timeWindowUnit: 's', + index: ['test-index'], + timeField: '@timestamp', + searchType: SearchType.searchSource, + searchConfiguration: {}, +}; + +const dataViewPluginMock = dataViewPluginMocks.createStartContract(); +const chartsStartMock = chartPluginMock.createStartContract(); +const unifiedSearchMock = unifiedSearchPluginMock.createStartContract(); +const httpMock = httpServiceMock.createStartContract(); +const docLinksMock = docLinksServiceMock.createStartContract(); +export const uiSettingsMock = { + get: jest.fn(), +} as unknown as IUiSettingsClient; + +const mockSearchResult = new Subject(); +const searchSourceFieldsMock = { + query: { + query: '', + language: 'kuery', + }, + filter: [], + index: { + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + title: 'kibana_sample_data_logs', + fields: [], + }, +}; + +const searchSourceMock = { + id: 'data_source6', + fields: searchSourceFieldsMock, + getField: (name: string) => { + return (searchSourceFieldsMock as Record<string, object>)[name] || ''; + }, + setField: jest.fn(), + createCopy: jest.fn(() => { + return searchSourceMock; + }), + setParent: jest.fn(() => { + return searchSourceMock; + }), + fetch$: jest.fn(() => { + return mockSearchResult; + }), +} as unknown as ISearchSource; + +const savedQueryMock = { + id: 'test-id', + attributes: { + title: 'test-filter-set', + description: '', + query: { + query: 'category.keyword : "Men\'s Shoes" ', + language: 'kuery', + }, + filters: [], + }, +}; + +const dataMock = dataPluginMock.createStartContract(); +(dataMock.search.searchSource.create as jest.Mock).mockImplementation(() => + Promise.resolve(searchSourceMock) +); +(dataMock.dataViews.getIdsWithTitle as jest.Mock).mockImplementation(() => Promise.resolve([])); +dataMock.dataViews.getDefaultDataView = jest.fn(() => Promise.resolve(null)); +(dataMock.query.savedQueries.getSavedQuery as jest.Mock).mockImplementation(() => + Promise.resolve(savedQueryMock) +); +dataMock.query.savedQueries.findSavedQueries = jest.fn(() => + Promise.resolve({ total: 0, queries: [] }) +); +(httpMock.post as jest.Mock).mockImplementation(() => Promise.resolve({ fields: [] })); + +const Wrapper: React.FC<{ + ruleParams: EsQueryAlertParams<SearchType.searchSource> | EsQueryAlertParams<SearchType.esQuery>; +}> = ({ ruleParams }) => { + const [currentRuleParams, setCurrentRuleParams] = useState<CommonAlertParams>(ruleParams); + const errors = { + index: [], + esQuery: [], + size: [], + timeField: [], + timeWindowSize: [], + searchConfiguration: [], + searchType: [], + }; + + return ( + <EsQueryAlertTypeExpression + ruleInterval="1m" + ruleThrottle="1m" + alertNotifyWhen="onThrottleInterval" + ruleParams={currentRuleParams} + setRuleParams={(name, value) => { + setCurrentRuleParams((params) => ({ ...params, [name]: value })); + }} + setRuleProperty={(name, params) => { + if (name === 'params') { + setCurrentRuleParams(params as CommonAlertParams); + } + }} + errors={errors} + unifiedSearch={unifiedSearchMock} + data={dataMock} + dataViews={dataViewPluginMock} + defaultActionGroupId="" + actionGroups={[]} + charts={chartsStartMock} + /> + ); +}; + +const setup = ( + ruleParams: EsQueryAlertParams<SearchType.searchSource> | EsQueryAlertParams<SearchType.esQuery> +) => { + return mountWithIntl( + <KibanaContextProvider + services={{ + data: dataMock, + uiSettings: uiSettingsMock, + docLinks: docLinksMock, + http: httpMock, + }} + > + <Wrapper ruleParams={ruleParams} /> + </KibanaContextProvider> + ); +}; + +describe('EsQueryAlertTypeExpression', () => { + test('should render options by default', async () => { + const wrapper = setup({} as EsQueryAlertParams<SearchType.esQuery>); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormType_searchSource').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormType_esQuery').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); + }); + + test('should switch to QueryDSL form type on selection and return back on cancel', async () => { + let wrapper = setup({} as EsQueryAlertParams<SearchType.esQuery>); + await act(async () => { + findTestSubject(wrapper, 'queryFormType_esQuery').simulate('click'); + }); + wrapper = await wrapper.update(); + + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeFalsy(); + expect(findTestSubject(wrapper, 'queryJsonEditor').exists()).toBeTruthy(); + expect(findTestSubject(wrapper, 'selectIndexExpression').exists()).toBeTruthy(); + + await act(async () => { + findTestSubject(wrapper, 'queryFormTypeChooserCancel').simulate('click'); + }); + wrapper = await wrapper.update(); + expect(findTestSubject(wrapper, 'selectIndexExpression').exists()).toBeFalsy(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); + }); + + test('should switch to KQL or Lucene form type on selection and return back on cancel', async () => { + let wrapper = setup({} as EsQueryAlertParams<SearchType.searchSource>); + await act(async () => { + findTestSubject(wrapper, 'queryFormType_searchSource').simulate('click'); + }); + wrapper = await wrapper.update(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeFalsy(); + expect(findTestSubject(wrapper, 'selectDataViewExpression').exists()).toBeTruthy(); + + await act(async () => { + findTestSubject(wrapper, 'queryFormTypeChooserCancel').simulate('click'); + }); + wrapper = await wrapper.update(); + expect(findTestSubject(wrapper, 'selectDataViewExpression').exists()).toBeFalsy(); + expect(findTestSubject(wrapper, 'queryFormTypeChooserTitle').exists()).toBeTruthy(); + }); + + test('should render QueryDSL view without the form type chooser if some rule params were passed', async () => { + let wrapper: ReactWrapper; + await act(async () => { + wrapper = setup(defaultEsQueryRuleParams); + wrapper = await wrapper.update(); + }); + expect(findTestSubject(wrapper!, 'queryFormTypeChooserTitle').exists()).toBeFalsy(); + expect(findTestSubject(wrapper!, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); + expect(findTestSubject(wrapper!, 'selectIndexExpression').exists()).toBeTruthy(); + }); + + test('should render KQL and Lucene view without the form type chooser if some rule params were passed', async () => { + let wrapper: ReactWrapper; + await act(async () => { + wrapper = setup(defaultSearchSourceRuleParams); + wrapper = await wrapper.update(); + }); + wrapper = await wrapper!.update(); + expect(findTestSubject(wrapper!, 'queryFormTypeChooserTitle').exists()).toBeFalsy(); + expect(findTestSubject(wrapper!, 'queryFormTypeChooserCancel').exists()).toBeFalsy(); + expect(findTestSubject(wrapper!, 'selectDataViewExpression').exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx index 3b5e978b999c8..8a56fdc4f41e3 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/expression.tsx @@ -5,17 +5,16 @@ * 2.0. */ -import React, { memo, PropsWithChildren } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { memo, PropsWithChildren, useCallback, useRef } from 'react'; import deepEqual from 'fast-deep-equal'; - import 'brace/theme/github'; - -import { EuiSpacer, EuiCallOut } from '@elastic/eui'; +import { EuiCallOut, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; -import { ErrorKey, EsQueryAlertParams } from '../types'; +import { EsQueryAlertParams, SearchType } from '../types'; import { SearchSourceExpression, SearchSourceExpressionProps } from './search_source_expression'; import { EsQueryExpression } from './es_query_expression'; +import { QueryFormTypeChooser } from './query_form_type_chooser'; import { isSearchSourceAlert } from '../util'; import { EXPRESSION_ERROR_KEYS } from '../constants'; @@ -36,38 +35,69 @@ const SearchSourceExpressionMemoized = memo<SearchSourceExpressionProps>( export const EsQueryAlertTypeExpression: React.FunctionComponent< RuleTypeParamsExpressionProps<EsQueryAlertParams> > = (props) => { - const { ruleParams, errors } = props; + const { ruleParams, errors, setRuleProperty, setRuleParams } = props; const isSearchSource = isSearchSourceAlert(ruleParams); + const isManagementPage = useRef(!Object.keys(ruleParams).length).current; - const hasExpressionErrors = Object.keys(errors).some((errorKey) => { - return ( - EXPRESSION_ERROR_KEYS.includes(errorKey as ErrorKey) && - errors[errorKey].length >= 1 && - ruleParams[errorKey] !== undefined - ); - }); + const formTypeSelected = useCallback( + (searchType: SearchType | null) => { + if (!searchType) { + // @ts-expect-error Reset rule params regardless of their type + setRuleProperty('params', {}); + return; + } + setRuleParams('searchType', searchType); + }, + [setRuleParams, setRuleProperty] + ); - const expressionErrorMessage = i18n.translate( + const expressionGenericErrorMessage = i18n.translate( 'xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage', { defaultMessage: 'Expression contains errors.', } ); + const errorParam = EXPRESSION_ERROR_KEYS.find((errorKey) => { + return errors[errorKey]?.length >= 1 && ruleParams[errorKey] !== undefined; + }); + + const expressionError = !!errorParam && ( + <> + <EuiCallOut + color="danger" + size="s" + title={ + ['index', 'searchType'].includes(errorParam) + ? errors[errorParam] + : expressionGenericErrorMessage + } + /> + <EuiSpacer /> + </> + ); + return ( <> - {hasExpressionErrors && ( - <> - <EuiCallOut color="danger" size="s" title={expressionErrorMessage} /> - <EuiSpacer /> - </> + {expressionError} + + {/* Showing the selected type */} + {isManagementPage && ( + <QueryFormTypeChooser + searchType={ruleParams.searchType as SearchType} + onFormTypeSelect={formTypeSelected} + /> )} - {isSearchSource ? ( + {ruleParams.searchType && isSearchSource && ( <SearchSourceExpressionMemoized {...props} ruleParams={ruleParams} /> - ) : ( + )} + + {ruleParams.searchType && !isSearchSource && ( <EsQueryExpression {...props} ruleParams={ruleParams} /> )} + + <EuiHorizontalRule /> </> ); }; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/query_form_type_chooser.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/query_form_type_chooser.tsx new file mode 100644 index 0000000000000..d4595ccde492e --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/query_form_type_chooser.tsx @@ -0,0 +1,131 @@ +/* + * 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 { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiListGroup, + EuiListGroupItem, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { i18n } from '@kbn/i18n'; +import { SearchType } from '../types'; + +const FORM_TYPE_ITEMS: Array<{ formType: SearchType; label: string; description: string }> = [ + { + formType: SearchType.searchSource, + label: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeLabel', + { + defaultMessage: 'KQL or Lucene', + } + ), + description: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.kqlOrLuceneFormTypeDescription', + { + defaultMessage: 'Use KQL or Lucene to define a text-based query.', + } + ), + }, + { + formType: SearchType.esQuery, + label: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeLabel', + { + defaultMessage: 'Query DSL', + } + ), + description: i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.queryDslFormTypeDescription', + { + defaultMessage: 'Use the Elasticsearch Query DSL to define a query.', + } + ), + }, +]; + +export interface QueryFormTypeProps { + searchType: SearchType | null; + onFormTypeSelect: (formType: SearchType | null) => void; +} + +export const QueryFormTypeChooser: React.FC<QueryFormTypeProps> = ({ + searchType, + onFormTypeSelect, +}) => { + if (searchType) { + const activeFormTypeItem = FORM_TYPE_ITEMS.find((item) => item.formType === searchType); + + return ( + <> + <EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}> + <EuiFlexItem> + <EuiTitle size="xs" data-test-subj="selectedRuleFormTypeTitle"> + <h5>{activeFormTypeItem?.label}</h5> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonIcon + iconType="cross" + color="danger" + data-test-subj="queryFormTypeChooserCancel" + aria-label={i18n.translate( + 'xpack.stackAlerts.esQuery.ui.selectQueryFormType.cancelSelectionAriaLabel', + { + defaultMessage: 'Cancel selection', + } + )} + onClick={() => onFormTypeSelect(null)} + /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiText color="subdued" size="s" data-test-subj="selectedRuleFormTypeDescription"> + {activeFormTypeItem?.description} + </EuiText> + <EuiSpacer /> + </> + ); + } + + return ( + <> + <EuiTitle size="xs"> + <h5 data-test-subj="queryFormTypeChooserTitle"> + <FormattedMessage + id="xpack.stackAlerts.esQuery.ui.selectQueryFormTypeLabel" + defaultMessage="Select a query type" + /> + </h5> + </EuiTitle> + <EuiListGroup flush gutterSize="m" size="l" maxWidth={false}> + {FORM_TYPE_ITEMS.map((item) => ( + <EuiListGroupItem + wrapText + key={`form-type-${item.formType}`} + data-test-subj={`queryFormType_${item.formType}`} + color="primary" + label={ + <span> + <strong>{item.label}</strong> + <EuiText color="subdued" size="s"> + <p>{item.description}</p> + </EuiText> + </span> + } + onClick={() => onFormTypeSelect(item.formType)} + /> + ))} + </EuiListGroup> + <EuiSpacer /> + </> + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx index b573bd3a662fd..a9f77d5f552df 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.test.tsx @@ -47,7 +47,13 @@ const defaultSearchSourceExpressionParams: EsQueryAlertParams<SearchType.searchS index: ['test-index'], timeField: '@timestamp', searchType: SearchType.searchSource, - searchConfiguration: {}, + searchConfiguration: { + query: { + query: '', + language: 'lucene', + }, + index: '90943e30-9a47-11e8-b64d-95841ca0b247', + }, }; const mockSearchResult = new Subject(); @@ -64,24 +70,24 @@ const testResultPartial = { running: true, }; +const searchSourceFieldsMock = { + query: { + query: '', + language: 'kuery', + }, + filter: [], + index: { + id: '90943e30-9a47-11e8-b64d-95841ca0b247', + title: 'kibana_sample_data_logs', + fields: [], + }, +}; + const searchSourceMock = { id: 'data_source6', - fields: { - query: { - query: '', - language: 'kuery', - }, - filter: [], - index: { - id: '90943e30-9a47-11e8-b64d-95841ca0b247', - title: 'kibana_sample_data_logs', - }, - }, + fields: searchSourceFieldsMock, getField: (name: string) => { - if (name === 'filter') { - return []; - } - return ''; + return (searchSourceFieldsMock as Record<string, object>)[name] || ''; }, setField: jest.fn(), createCopy: jest.fn(() => { @@ -176,6 +182,7 @@ const dataMock = dataPluginMock.createStartContract(); Promise.resolve(searchSourceMock) ); (dataMock.dataViews.getIdsWithTitle as jest.Mock).mockImplementation(() => Promise.resolve([])); +dataMock.dataViews.getDefaultDataView = jest.fn(() => Promise.resolve(null)); (dataMock.query.savedQueries.getSavedQuery as jest.Mock).mockImplementation(() => Promise.resolve(savedQueryMock) ); @@ -225,6 +232,17 @@ describe('SearchSourceAlertTypeExpression', () => { expect(findTestSubject(wrapper, 'thresholdExpression')).toBeTruthy(); }); + test('should disable Test Query button if data view is not selected yet', async () => { + let wrapper = setup({ ...defaultSearchSourceExpressionParams, searchConfiguration: undefined }); + await act(async () => { + await nextTick(); + }); + wrapper = await wrapper.update(); + + const testButton = findTestSubject(wrapper, 'testQuery'); + expect(testButton.prop('disabled')).toBeTruthy(); + }); + test('should show success message if Test Query is successful', async () => { let wrapper = setup(defaultSearchSourceExpressionParams); await act(async () => { @@ -232,7 +250,9 @@ describe('SearchSourceAlertTypeExpression', () => { }); wrapper = await wrapper.update(); await act(async () => { - findTestSubject(wrapper, 'testQuery').simulate('click'); + const testButton = findTestSubject(wrapper, 'testQuery'); + expect(testButton.prop('disabled')).toBeFalsy(); + testButton.simulate('click'); wrapper.update(); }); wrapper = await wrapper.update(); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx index 26b2d074bfd8b..b00176288b751 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression.tsx @@ -27,12 +27,13 @@ export const SearchSourceExpression = ({ setRuleProperty, }: SearchSourceExpressionProps) => { const { - searchConfiguration, thresholdComparator, threshold, timeWindowSize, timeWindowUnit, size, + savedQueryId, + searchConfiguration, } = ruleParams; const { data } = useTriggersAndActionsUiDeps(); @@ -46,31 +47,45 @@ export const SearchSourceExpression = ({ ); useEffect(() => { - setRuleProperty('params', { - searchConfiguration, - searchType: SearchType.searchSource, - timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, - timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, - threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, - thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, - size: size ?? DEFAULT_VALUES.SIZE, - }); + const initSearchSource = async () => { + let initialSearchConfiguration = searchConfiguration; + + // Init searchConfiguration when creating rule from Stack Management page + if (!searchConfiguration) { + const newSearchSource = data.search.searchSource.createEmpty(); + newSearchSource.setField('query', data.query.queryString.getDefaultQuery()); + const defaultDataView = await data.dataViews.getDefaultDataView(); + if (defaultDataView) { + newSearchSource.setField('index', defaultDataView); + } + initialSearchConfiguration = newSearchSource.getSerializedFields(); + } + + setRuleProperty('params', { + searchConfiguration: initialSearchConfiguration, + searchType: SearchType.searchSource, + timeWindowSize: timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, + threshold: threshold ?? DEFAULT_VALUES.THRESHOLD, + thresholdComparator: thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, + size: size ?? DEFAULT_VALUES.SIZE, + }); - const initSearchSource = () => data.search.searchSource - .create(searchConfiguration) + .create(initialSearchConfiguration) .then((fetchedSearchSource) => setSearchSource(fetchedSearchSource)) .catch(setParamsError); + }; initSearchSource(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [data.search.searchSource, data.dataViews]); useEffect(() => { - if (ruleParams.savedQueryId) { - data.query.savedQueries.getSavedQuery(ruleParams.savedQueryId).then(setSavedQuery); + if (savedQueryId) { + data.query.savedQueries.getSavedQuery(savedQueryId).then(setSavedQuery); } - }, [data.query.savedQueries, ruleParams.savedQueryId]); + }, [data.query.savedQueries, savedQueryId]); if (paramsError) { return ( @@ -89,9 +104,9 @@ export const SearchSourceExpression = ({ return ( <SearchSourceExpressionForm + ruleParams={ruleParams} searchSource={searchSource} errors={errors} - ruleParams={ruleParams} initialSavedQuery={savedQuery} setParam={setParam} /> diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx index 4623e26b04d34..1a6ad28eea9fe 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/search_source_expression_form.tsx @@ -11,36 +11,40 @@ import { lastValueFrom } from 'rxjs'; import { Filter } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { DataView, Query, ISearchSource, getTime } from '@kbn/data-plugin/common'; -import { - ForLastExpression, - IErrorObject, - ThresholdExpression, - ValueExpression, -} from '@kbn/triggers-actions-ui-plugin/public'; -import { SearchBar } from '@kbn/unified-search-plugin/public'; +import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { SearchBar, SearchBarProps } from '@kbn/unified-search-plugin/public'; import { mapAndFlattenFilters, SavedQuery, TimeHistory } from '@kbn/data-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { EsQueryAlertParams, SearchType } from '../types'; +import { CommonAlertParams, EsQueryAlertParams, SearchType } from '../types'; import { DEFAULT_VALUES } from '../constants'; import { DataViewSelectPopover } from '../../components/data_view_select_popover'; import { useTriggersAndActionsUiDeps } from '../util'; -import { totalHitsToNumber } from './use_test_query'; -import { TestQueryRow } from './test_query_row'; +import { RuleCommonExpressions } from '../rule_common_expressions'; +import { totalHitsToNumber } from '../test_query_row'; +import { hasExpressionValidationErrors } from '../validation'; + +const HIDDEN_FILTER_PANEL_OPTIONS: SearchBarProps['hiddenFilterPanelOptions'] = [ + 'pinFilter', + 'disableFilter', +]; interface LocalState { index: DataView; filter: Filter[]; query: Query; - threshold: number[]; - timeWindowSize: number; - size: number; + thresholdComparator: CommonAlertParams['thresholdComparator']; + threshold: CommonAlertParams['threshold']; + timeWindowSize: CommonAlertParams['timeWindowSize']; + timeWindowUnit: CommonAlertParams['timeWindowUnit']; + size: CommonAlertParams['size']; } interface LocalStateAction { - type: SearchSourceParamsAction['type'] | ('threshold' | 'timeWindowSize' | 'size'); - payload: SearchSourceParamsAction['payload'] | (number[] | number); + type: + | SearchSourceParamsAction['type'] + | ('threshold' | 'thresholdComparator' | 'timeWindowSize' | 'timeWindowUnit' | 'size'); + payload: SearchSourceParamsAction['payload'] | (number[] | number | string); } type LocalStateReducer = (prevState: LocalState, action: LocalStateAction) => LocalState; @@ -64,8 +68,7 @@ const isSearchSourceParam = (action: LocalStateAction): action is SearchSourcePa export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProps) => { const { data } = useTriggersAndActionsUiDeps(); - const { searchSource, ruleParams, errors, initialSavedQuery, setParam } = props; - const { thresholdComparator, timeWindowUnit } = ruleParams; + const { searchSource, errors, initialSavedQuery, setParam, ruleParams } = props; const [savedQuery, setSavedQuery] = useState<SavedQuery>(); const timeHistory = useMemo(() => new TimeHistory(new Storage(localStorage)), []); @@ -86,20 +89,15 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp index: searchSource.getField('index')!, query: searchSource.getField('query')!, filter: mapAndFlattenFilters(searchSource.getField('filter') as Filter[]), - threshold: ruleParams.threshold, - timeWindowSize: ruleParams.timeWindowSize, - size: ruleParams.size, + threshold: ruleParams.threshold ?? DEFAULT_VALUES.THRESHOLD, + thresholdComparator: ruleParams.thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR, + timeWindowSize: ruleParams.timeWindowSize ?? DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: ruleParams.timeWindowUnit ?? DEFAULT_VALUES.TIME_WINDOW_UNIT, + size: ruleParams.size ?? DEFAULT_VALUES.SIZE, } ); - const { - index: dataView, - query, - filter: filters, - threshold, - timeWindowSize, - size, - } = ruleConfiguration; - const dataViews = useMemo(() => [dataView], [dataView]); + const { index: dataView, query, filter: filters } = ruleConfiguration; + const dataViews = useMemo(() => (dataView ? [dataView] : []), [dataView]); const onSelectDataView = useCallback( (newDataViewId) => @@ -145,8 +143,9 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp // window size const onChangeWindowUnit = useCallback( - (selectedWindowUnit: string) => setParam('timeWindowUnit', selectedWindowUnit), - [setParam] + (selectedWindowUnit: string) => + dispatch({ type: 'timeWindowUnit', payload: selectedWindowUnit }), + [] ); const onChangeWindowSize = useCallback( @@ -158,8 +157,9 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp // threshold const onChangeSelectedThresholdComparator = useCallback( (selectedThresholdComparator?: string) => - setParam('thresholdComparator', selectedThresholdComparator), - [setParam] + selectedThresholdComparator && + dispatch({ type: 'thresholdComparator', payload: selectedThresholdComparator }), + [] ); const onChangeSelectedThreshold = useCallback( @@ -173,7 +173,7 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp [] ); - const timeWindow = `${timeWindowSize}${timeWindowUnit}`; + const timeWindow = `${ruleConfiguration.timeWindowSize}${ruleConfiguration.timeWindowUnit}`; const createTestSearchSource = useCallback(() => { const testSearchSource = searchSource.createCopy(); @@ -204,8 +204,8 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp <EuiTitle size="xs"> <h5> <FormattedMessage - id="xpack.stackAlerts.searchThreshold.ui.conditionPrompt" - defaultMessage="When the number of documents match" + id="xpack.stackAlerts.esQuery.ui.selectDataViewPrompt" + defaultMessage="Select a data view" /> </h5> </EuiTitle> @@ -213,95 +213,70 @@ export const SearchSourceExpressionForm = (props: SearchSourceExpressionFormProp <EuiSpacer size="s" /> <DataViewSelectPopover - initialDataViewTitle={dataView.title} - initialDataViewId={dataView.id} + dataViewName={dataView?.getName?.() ?? dataView?.title} + dataViewId={dataView?.id} onSelectDataView={onSelectDataView} /> - <EuiSpacer size="s" /> - - <SearchBar - onQuerySubmit={onQueryBarSubmit} - onQueryChange={onChangeQuery} - suggestionsSize="s" - displayStyle="inPage" - placeholder={i18n.translate('xpack.stackAlerts.searchSource.ui.searchQuery', { - defaultMessage: 'Search query', - })} - query={query} - indexPatterns={dataViews} - savedQuery={savedQuery} - filters={filters} - onFiltersUpdated={onUpdateFilters} - onClearSavedQuery={onClearSavedQuery} - onSavedQueryUpdated={onSavedQuery} - onSaved={onSavedQuery} - showSaveQuery={true} - showQueryBar={true} - showQueryInput={true} - showFilterBar={true} - showDatePicker={false} - showAutoRefreshOnly={false} - showSubmitButton={false} - dateRangeFrom={undefined} - dateRangeTo={undefined} - timeHistory={timeHistory} - hiddenFilterPanelOptions={['pinFilter', 'disableFilter']} - /> - - <EuiSpacer size="s" /> - <EuiTitle size="xs"> - <h5> - <FormattedMessage - id="xpack.stackAlerts.searchSource.ui.conditionPrompt" - defaultMessage="When the number of matches" + {Boolean(dataView?.id) && ( + <> + <EuiSpacer size="s" /> + <EuiTitle size="xs"> + <h5> + <FormattedMessage + id="xpack.stackAlerts.esQuery.ui.defineTextQueryPrompt" + defaultMessage="Define your query" + /> + </h5> + </EuiTitle> + <EuiSpacer size="xs" /> + <SearchBar + onQuerySubmit={onQueryBarSubmit} + onQueryChange={onChangeQuery} + suggestionsSize="s" + displayStyle="inPage" + query={query} + indexPatterns={dataViews} + savedQuery={savedQuery} + filters={filters} + onFiltersUpdated={onUpdateFilters} + onClearSavedQuery={onClearSavedQuery} + onSavedQueryUpdated={onSavedQuery} + onSaved={onSavedQuery} + showSaveQuery + showQueryBar + showQueryInput + showFilterBar + showDatePicker={false} + showAutoRefreshOnly={false} + showSubmitButton={false} + dateRangeFrom={undefined} + dateRangeTo={undefined} + timeHistory={timeHistory} + hiddenFilterPanelOptions={HIDDEN_FILTER_PANEL_OPTIONS} /> - </h5> - </EuiTitle> - <EuiSpacer size="s" /> - <ThresholdExpression - data-test-subj="thresholdExpression" - thresholdComparator={thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR} - threshold={threshold ?? DEFAULT_VALUES.THRESHOLD} - errors={errors} - display="fullWidth" - popupPosition={'upLeft'} - onChangeSelectedThreshold={onChangeSelectedThreshold} - onChangeSelectedThresholdComparator={onChangeSelectedThresholdComparator} - /> - <ForLastExpression - data-test-subj="forLastExpression" - popupPosition={'upLeft'} - timeWindowSize={timeWindowSize} - timeWindowUnit={timeWindowUnit} - display="fullWidth" - errors={errors} + </> + )} + + <EuiSpacer size="m" /> + + <RuleCommonExpressions + threshold={ruleConfiguration.threshold} + thresholdComparator={ruleConfiguration.thresholdComparator} + timeWindowSize={ruleConfiguration.timeWindowSize} + timeWindowUnit={ruleConfiguration.timeWindowUnit} + size={ruleConfiguration.size} + onChangeThreshold={onChangeSelectedThreshold} + onChangeThresholdComparator={onChangeSelectedThresholdComparator} onChangeWindowSize={onChangeWindowSize} onChangeWindowUnit={onChangeWindowUnit} + onChangeSizeValue={onChangeSizeValue} + errors={errors} + hasValidationErrors={hasExpressionValidationErrors(ruleParams) || !dataView} + onTestFetch={onTestFetch} + onCopyQuery={onCopyQuery} /> - <EuiSpacer size="s" /> - <EuiTitle size="xs"> - <h5> - <FormattedMessage - id="xpack.stackAlerts.searchSource.ui.selectSizePrompt" - defaultMessage="Select a size" - /> - </h5> - </EuiTitle> - <EuiSpacer size="s" /> - <ValueExpression - description={i18n.translate('xpack.stackAlerts.searchSource.ui.sizeExpression', { - defaultMessage: 'Size', - })} - data-test-subj="sizeValueExpression" - value={size} - errors={errors.size} - display="fullWidth" - popupPosition={'upLeft'} - onChangeSelectedValue={onChangeSizeValue} - /> - <EuiSpacer size="s" /> - <TestQueryRow fetch={onTestFetch} copyQuery={onCopyQuery} hasValidationErrors={false} /> + <EuiSpacer /> </Fragment> ); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/rule_common_expressions/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/rule_common_expressions/index.ts new file mode 100644 index 0000000000000..7a1eff20adc71 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/rule_common_expressions/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 { RuleCommonExpressions } from './rule_common_expressions'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/rule_common_expressions/rule_common_expressions.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/rule_common_expressions/rule_common_expressions.tsx new file mode 100644 index 0000000000000..684b340170139 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/rule_common_expressions/rule_common_expressions.tsx @@ -0,0 +1,132 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem, EuiIconTip, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + ForLastExpression, + IErrorObject, + ThresholdExpression, + ValueExpression, +} from '@kbn/triggers-actions-ui-plugin/public'; +import { CommonAlertParams } from '../types'; +import { DEFAULT_VALUES } from '../constants'; +import { TestQueryRow, TestQueryRowProps } from '../test_query_row'; + +export interface RuleCommonExpressionsProps { + thresholdComparator?: CommonAlertParams['thresholdComparator']; + threshold?: CommonAlertParams['threshold']; + timeWindowSize: CommonAlertParams['timeWindowSize']; + timeWindowUnit: CommonAlertParams['timeWindowUnit']; + size: CommonAlertParams['size']; + errors: IErrorObject; + hasValidationErrors: boolean; + onChangeThreshold: Parameters<typeof ThresholdExpression>[0]['onChangeSelectedThreshold']; + onChangeThresholdComparator: Parameters< + typeof ThresholdExpression + >[0]['onChangeSelectedThresholdComparator']; + onChangeWindowSize: Parameters<typeof ForLastExpression>[0]['onChangeWindowSize']; + onChangeWindowUnit: Parameters<typeof ForLastExpression>[0]['onChangeWindowUnit']; + onChangeSizeValue: Parameters<typeof ValueExpression>[0]['onChangeSelectedValue']; + onTestFetch: TestQueryRowProps['fetch']; + onCopyQuery?: TestQueryRowProps['copyQuery']; +} + +export const RuleCommonExpressions: React.FC<RuleCommonExpressionsProps> = ({ + thresholdComparator, + threshold, + timeWindowSize, + timeWindowUnit, + size, + errors, + hasValidationErrors, + onChangeThreshold, + onChangeThresholdComparator, + onChangeWindowSize, + onChangeWindowUnit, + onChangeSizeValue, + onTestFetch, + onCopyQuery, +}) => { + return ( + <> + <EuiTitle size="xs"> + <h5> + <FormattedMessage + id="xpack.stackAlerts.esQuery.ui.conditionsPrompt" + defaultMessage="Set the threshold and duration" + /> + </h5> + </EuiTitle> + <EuiSpacer size="s" /> + <ThresholdExpression + data-test-subj="thresholdExpression" + thresholdComparator={thresholdComparator ?? DEFAULT_VALUES.THRESHOLD_COMPARATOR} + threshold={threshold ?? DEFAULT_VALUES.THRESHOLD} + errors={errors} + display="fullWidth" + popupPosition="upLeft" + onChangeSelectedThreshold={onChangeThreshold} + onChangeSelectedThresholdComparator={onChangeThresholdComparator} + /> + <ForLastExpression + data-test-subj="forLastExpression" + popupPosition="upLeft" + timeWindowSize={timeWindowSize} + timeWindowUnit={timeWindowUnit} + display="fullWidth" + errors={errors} + onChangeWindowSize={onChangeWindowSize} + onChangeWindowUnit={onChangeWindowUnit} + /> + <EuiSpacer size="s" /> + <EuiFlexGroup alignItems="center" responsive={false} gutterSize="xs"> + <EuiFlexItem grow={false}> + <EuiTitle size="xs"> + <h5> + <FormattedMessage + id="xpack.stackAlerts.esQuery.ui.selectSizePrompt" + defaultMessage="Set the number of documents to send" + /> + </h5> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiIconTip + position="right" + color="subdued" + type="questionInCircle" + content={i18n.translate('xpack.stackAlerts.esQuery.ui.selectSizePrompt.toolTip', { + defaultMessage: + 'Specify the number of documents to pass to the configured actions when the threshold condition is met.', + })} + /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="s" /> + <ValueExpression + description={i18n.translate('xpack.stackAlerts.esQuery.ui.sizeExpression', { + defaultMessage: 'Size', + })} + data-test-subj="sizeValueExpression" + value={size} + errors={errors.size} + display="fullWidth" + popupPosition="upLeft" + onChangeSelectedValue={onChangeSizeValue} + /> + <EuiSpacer size="s" /> + <TestQueryRow + fetch={onTestFetch} + copyQuery={onCopyQuery} + hasValidationErrors={hasValidationErrors} + /> + </> + ); +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/index.ts new file mode 100644 index 0000000000000..51e09eb17fbd8 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/index.ts @@ -0,0 +1,10 @@ +/* + * 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 { TestQueryRow } from './test_query_row'; +export type { TestQueryRowProps } from './test_query_row'; +export { useTestQuery, totalHitsToNumber } from './use_test_query'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/test_query_row.test.tsx similarity index 100% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.test.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/test_query_row.test.tsx diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/test_query_row.tsx similarity index 97% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/test_query_row.tsx index 405b1e47de8d8..04eeb86945cb3 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/test_query_row.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/test_query_row.tsx @@ -18,15 +18,17 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { useTestQuery } from './use_test_query'; -export function TestQueryRow({ - fetch, - copyQuery, - hasValidationErrors, -}: { +export interface TestQueryRowProps { fetch: () => Promise<{ nrOfDocs: number; timeWindow: string }>; copyQuery?: () => string; hasValidationErrors: boolean; -}) { +} + +export const TestQueryRow: React.FC<TestQueryRowProps> = ({ + fetch, + copyQuery, + hasValidationErrors, +}) => { const { onTestQuery, testQueryResult, testQueryError, testQueryLoading } = useTestQuery(fetch); const [copiedMessage, setCopiedMessage] = useState<ReactNode | null>(null); @@ -119,4 +121,4 @@ export function TestQueryRow({ )} </> ); -} +}; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/use_test_query.test.ts similarity index 100% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.test.ts rename to x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/use_test_query.test.ts diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/use_test_query.ts similarity index 100% rename from x-pack/plugins/stack_alerts/public/alert_types/es_query/expression/use_test_query.ts rename to x-pack/plugins/stack_alerts/public/alert_types/es_query/test_query_row/use_test_query.ts diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts index 703570ad5faae..8844c9e10c588 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/types.ts @@ -9,6 +9,7 @@ import { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { SerializedSearchSourceFields } from '@kbn/data-plugin/common'; import { EuiComboBoxOptionOption } from '@elastic/eui'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { EXPRESSION_ERRORS } from './constants'; export interface Comparator { @@ -40,8 +41,8 @@ export interface OnlyEsQueryAlertParams { timeField: string; } export interface OnlySearchSourceAlertParams { - searchType: 'searchSource'; - searchConfiguration: SerializedSearchSourceFields; + searchType?: 'searchSource'; + searchConfiguration?: SerializedSearchSourceFields; savedQueryId?: string; } @@ -53,4 +54,5 @@ export type ErrorKey = keyof ExpressionErrors & unknown; export interface TriggersAndActionsUiDeps { data: DataPublicPluginStart; + dataViewEditor: DataViewEditorStart; } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts index 90b7f96b781b9..9c485455cf19c 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.test.ts @@ -6,9 +6,16 @@ */ import { EsQueryAlertParams, SearchType } from './types'; -import { validateExpression } from './validation'; +import { validateExpression, hasExpressionValidationErrors } from './validation'; describe('expression params validation', () => { + test('if params are not set should return a proper error message', () => { + const initialParams: EsQueryAlertParams<SearchType.esQuery> = + {} as EsQueryAlertParams<SearchType.esQuery>; + expect(validateExpression(initialParams).errors.searchType.length).toBeGreaterThan(0); + expect(validateExpression(initialParams).errors.searchType[0]).toBe('Query type is required.'); + }); + test('if index property is invalid should return proper error message', () => { const initialParams: EsQueryAlertParams<SearchType.esQuery> = { index: [], @@ -63,6 +70,7 @@ describe('expression params validation', () => { }; expect(validateExpression(initialParams).errors.esQuery.length).toBeGreaterThan(0); expect(validateExpression(initialParams).errors.esQuery[0]).toBe(`Query field is required.`); + expect(hasExpressionValidationErrors(initialParams)).toBe(true); }); test('if searchConfiguration property is not set should return proper error message', () => { @@ -157,4 +165,18 @@ describe('expression params validation', () => { 'Size must be between 0 and 10,000.' ); }); + + test('should not return error messages if all is correct', () => { + const initialParams: EsQueryAlertParams<SearchType.esQuery> = { + index: ['test'], + esQuery: '{"query":{"match_all":{}}}', + size: 250, + timeWindowSize: 100, + timeWindowUnit: 's', + threshold: [0], + timeField: '@timestamp', + }; + expect(validateExpression(initialParams).errors.size.length).toBe(0); + expect(hasExpressionValidationErrors(initialParams)).toBe(false); + }); }); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts index 8a1135e75492f..9e1e0df84052c 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/validation.ts @@ -12,11 +12,22 @@ import { EsQueryAlertParams, ExpressionErrors } from './types'; import { isSearchSourceAlert } from './util'; import { EXPRESSION_ERRORS } from './constants'; -export const validateExpression = (alertParams: EsQueryAlertParams): ValidationResult => { - const { size, threshold, timeWindowSize, thresholdComparator } = alertParams; +export const validateExpression = (ruleParams: EsQueryAlertParams): ValidationResult => { + const { size, threshold, timeWindowSize, thresholdComparator } = ruleParams; const validationResult = { errors: {} }; const errors: ExpressionErrors = defaultsDeep({}, EXPRESSION_ERRORS); validationResult.errors = errors; + + if (!('index' in ruleParams) && !ruleParams.searchType) { + errors.searchType.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredSearchType', { + defaultMessage: 'Query type is required.', + }) + ); + + return validationResult; + } + if (!threshold || threshold.length === 0 || threshold[0] === undefined) { errors.threshold0.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredThreshold0Text', { @@ -72,9 +83,9 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR * Skip esQuery and index params check if it is search source alert, * since it should contain searchConfiguration instead of esQuery and index. */ - const isSearchSource = isSearchSourceAlert(alertParams); + const isSearchSource = isSearchSourceAlert(ruleParams); if (isSearchSource) { - if (!alertParams.searchConfiguration) { + if (!ruleParams.searchConfiguration) { errors.searchConfiguration.push( i18n.translate( 'xpack.stackAlerts.esQuery.ui.validation.error.requiredSearchConfiguration', @@ -83,11 +94,17 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR } ) ); + } else if (!ruleParams.searchConfiguration.index) { + errors.index.push( + i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredDataViewText', { + defaultMessage: 'Data view is required.', + }) + ); } return validationResult; } - if (!alertParams.index || alertParams.index.length === 0) { + if (!ruleParams.index || ruleParams.index.length === 0) { errors.index.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredIndexText', { defaultMessage: 'Index is required.', @@ -95,7 +112,7 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR ); } - if (!alertParams.timeField) { + if (!ruleParams.timeField) { errors.timeField.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredTimeFieldText', { defaultMessage: 'Time field is required.', @@ -103,7 +120,7 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR ); } - if (!alertParams.esQuery) { + if (!ruleParams.esQuery) { errors.esQuery.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredQueryText', { defaultMessage: 'Elasticsearch query is required.', @@ -111,7 +128,7 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR ); } else { try { - const parsedQuery = JSON.parse(alertParams.esQuery); + const parsedQuery = JSON.parse(ruleParams.esQuery); if (!parsedQuery.query) { errors.esQuery.push( i18n.translate('xpack.stackAlerts.esQuery.ui.validation.error.requiredEsQueryText', { @@ -130,3 +147,10 @@ export const validateExpression = (alertParams: EsQueryAlertParams): ValidationR return validationResult; }; + +export const hasExpressionValidationErrors = (ruleParams: EsQueryAlertParams) => { + const { errors: validationErrors } = validateExpression(ruleParams); + return Object.keys(validationErrors).some( + (key) => validationErrors[key] && validationErrors[key].length + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx new file mode 100644 index 0000000000000..671e661f4eb06 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/step_duration_panel.tsx @@ -0,0 +1,65 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText, EuiTitle } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { ReportTypes } from '@kbn/observability-plugin/public'; +import { useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; + +import { ClientPluginsStart } from '../../../../../plugin'; +export const StepDurationPanel = () => { + const { observability } = useKibana<ClientPluginsStart>().services; + + const { ExploratoryViewEmbeddable } = observability; + + const { monitorId } = useParams<{ monitorId: string }>(); + + return ( + <EuiPanel> + <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiFlexItem grow={false}> + <EuiTitle size="xs"> + <h3>{DURATION_BY_STEP_LABEL}</h3> + </EuiTitle> + </EuiFlexItem> + <EuiFlexItem> + <EuiText size="s" color="subdued"> + {LAST_24H_LABEL} + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> + + <ExploratoryViewEmbeddable + axisTitlesVisibility={{ yLeft: false, yRight: false, x: false }} + customHeight={'300px'} + reportType={ReportTypes.KPI} + attributes={[ + { + name: DURATION_BY_STEP_LABEL, + reportDefinitions: { 'monitor.id': [monitorId] }, + selectedMetricField: 'synthetics.step.duration.us', + dataType: 'synthetics', + time: { from: 'now-24h/h', to: 'now' }, + breakdown: 'synthetics.step.name.keyword', + operationType: 'last_value', + seriesType: 'area_stacked', + }, + ]} + /> + </EuiPanel> + ); +}; + +const DURATION_BY_STEP_LABEL = i18n.translate('xpack.synthetics.detailsPanel.durationByStep', { + defaultMessage: 'Duration by step', +}); + +const LAST_24H_LABEL = i18n.translate('xpack.synthetics.detailsPanel.last24Hours', { + defaultMessage: 'Last 24 hours', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx index 0184cb5f8f150..41309a7cdc0b9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_summary/tabs_content/summary_tab_content.tsx @@ -6,26 +6,40 @@ */ import React from 'react'; -import { EuiTitle, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiTitle, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { StepDurationPanel } from './step_duration_panel'; import { MonitorDetailsPanel } from './monitor_details_panel'; export const SummaryTabContent = () => { return ( - <EuiFlexGroup> - <EuiFlexItem grow={1}> - <EuiPanel> - <EuiTitle size="xs"> - <h3>{MONITOR_DETAILS_LABEL}</h3> - </EuiTitle> - <MonitorDetailsPanel /> - </EuiPanel> - </EuiFlexItem> - <EuiFlexItem grow={2}> - <EuiPanel /> - </EuiFlexItem> - </EuiFlexGroup> + <> + <EuiFlexGroup> + <EuiFlexItem grow={1}> + <EuiPanel> + <EuiTitle size="xs"> + <h3>{MONITOR_DETAILS_LABEL}</h3> + </EuiTitle> + <MonitorDetailsPanel /> + </EuiPanel> + </EuiFlexItem> + <EuiFlexItem grow={2}> + <EuiPanel /> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="s" /> + <EuiPanel style={{ height: 100 }}>{/* TODO: Add status panel*/}</EuiPanel> + <EuiSpacer size="s" /> + <EuiFlexGroup> + <EuiFlexItem> + <EuiPanel>{/* TODO: Add last run panel*/}</EuiPanel> + </EuiFlexItem> + <EuiFlexItem> + <StepDurationPanel /> + </EuiFlexItem> + </EuiFlexGroup> + </> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx index 9d92c584592d3..aad917bccdee2 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx @@ -44,6 +44,9 @@ export const Actions = ({ euiTheme, id, name, reloadPage, canEditSynthetics }: P // TODO: Move deletion logic to redux state useEffect(() => { + if (!isDeleting) { + return; + } if ( monitorDeleteStatus === FETCH_STATUS.SUCCESS || monitorDeleteStatus === FETCH_STATUS.FAILURE @@ -71,7 +74,7 @@ export const Actions = ({ euiTheme, id, name, reloadPage, canEditSynthetics }: P { toastLifeTimeMs: 3000 } ); } - }, [setIsDeleting, reloadPage, monitorDeleteStatus]); + }, [setIsDeleting, isDeleting, reloadPage, monitorDeleteStatus]); const openPopover = () => { setIsPopoverOpen(true); diff --git a/x-pack/plugins/synthetics/public/plugin.ts b/x-pack/plugins/synthetics/public/plugin.ts index 826beb2cbfe68..636bc5f378672 100644 --- a/x-pack/plugins/synthetics/public/plugin.ts +++ b/x-pack/plugins/synthetics/public/plugin.ts @@ -159,16 +159,18 @@ export class UptimePlugin const appKeywords = [ 'Synthetics', - 'pings', - 'checks', 'availability', + 'browser', + 'checks', + 'digital', + 'reachability', + 'reachable', 'response duration', 'response time', + 'monitors', 'outside in', - 'reachability', - 'reachable', - 'digital', 'performance', + 'pings', 'web performance', 'web perf', ]; diff --git a/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts b/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts index 0b5e046743627..8bd7f5de80810 100644 --- a/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts +++ b/x-pack/plugins/task_manager/server/lib/calculate_health_status.ts @@ -14,6 +14,7 @@ import { TaskManagerConfig } from '../config'; export function calculateHealthStatus( summarizedStats: RawMonitoringStats, config: TaskManagerConfig, + shouldRunTasks: boolean, logger: Logger ): HealthStatus { const now = Date.now(); @@ -30,9 +31,12 @@ export function calculateHealthStatus( return HealthStatus.Error; } - if (hasExpiredHotTimestamps(summarizedStats, now, requiredHotStatsFreshness)) { - logger.debug('setting HealthStatus.Error because of expired hot timestamps'); - return HealthStatus.Error; + // Hot timestamps look at runtime stats which are not available when tasks are not running + if (shouldRunTasks) { + if (hasExpiredHotTimestamps(summarizedStats, now, requiredHotStatsFreshness)) { + logger.debug('setting HealthStatus.Error because of expired hot timestamps'); + return HealthStatus.Error; + } } if (hasExpiredColdTimestamps(summarizedStats, now, requiredColdStatsFreshness)) { diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts index 9b1b10127abe2..08c93f12ecad8 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts @@ -39,16 +39,16 @@ describe('logHealthMetrics', () => { // We must change from OK to Warning (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation( () => HealthStatus.Warning ); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); // We must change from OK to Error (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.Error); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); const debugCalls = (logger as jest.Mocked<Logger>).debug.mock.calls; const performanceMessage = /^Task Manager detected a degradation in performance/; @@ -76,9 +76,9 @@ describe('logHealthMetrics', () => { (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation( () => HealthStatus.Warning ); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); expect((logger as jest.Mocked<Logger>).warn).not.toHaveBeenCalled(); }); @@ -96,9 +96,9 @@ describe('logHealthMetrics', () => { // We must change from Error to OK (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.Error); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.OK); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); expect((logger as jest.Mocked<Logger>).warn).not.toHaveBeenCalled(); }); @@ -112,7 +112,7 @@ describe('logHealthMetrics', () => { }); const health = getMockMonitoredHealth(); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); const firstDebug = JSON.parse( (logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '') @@ -130,7 +130,7 @@ describe('logHealthMetrics', () => { }); const health = getMockMonitoredHealth(); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); const firstDebug = JSON.parse( (logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '') @@ -152,7 +152,7 @@ describe('logHealthMetrics', () => { () => HealthStatus.Warning ); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); const logMessage = JSON.parse( ((logger as jest.Mocked<Logger>).warn.mock.calls[0][0] as string).replace( @@ -175,7 +175,7 @@ describe('logHealthMetrics', () => { const { calculateHealthStatus } = jest.requireMock('./calculate_health_status'); (calculateHealthStatus as jest.Mock<HealthStatus>).mockImplementation(() => HealthStatus.Error); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); const logMessage = JSON.parse( ((logger as jest.Mocked<Logger>).error.mock.calls[0][0] as string).replace( @@ -214,7 +214,7 @@ describe('logHealthMetrics', () => { }, }); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); expect((logger as jest.Mocked<Logger>).warn.mock.calls[0][0] as string).toBe( `Detected delay task start of 60s for task(s) \"taskType:test\" (which exceeds configured value of 60s)` @@ -257,7 +257,7 @@ describe('logHealthMetrics', () => { }, }); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); expect((logger as jest.Mocked<Logger>).warn.mock.calls[0][0] as string).toBe( `Detected delay task start of 60s for task(s) \"taskType:test, taskType:test2\" (which exceeds configured value of 60s)` @@ -288,7 +288,43 @@ describe('logHealthMetrics', () => { stats: {}, }; - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); + + const firstDebug = JSON.parse( + (logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '') + ); + expect(firstDebug).toMatchObject(health); + }); + + it('should log as debug if shouldRunTasks is false', () => { + const logger = loggingSystemMock.create().get(); + const config = getTaskManagerConfig({ + monitored_stats_health_verbose_log: { + enabled: true, + warn_delayed_task_start_in_seconds: 60, + }, + }); + const health = getMockMonitoredHealth({ + stats: { + runtime: { + value: { + drift_by_type: { + 'taskType:test': { + p99: 60000, + }, + 'taskType:test2': { + p99: 60000, + }, + }, + drift: { + p99: 60000, + }, + }, + }, + }, + }); + + logHealthMetrics(health, logger, config, false); const firstDebug = JSON.parse( (logger as jest.Mocked<Logger>).debug.mock.calls[0][0].replace('Latest Monitored Stats: ', '') @@ -312,7 +348,7 @@ describe('logHealthMetrics', () => { }, }); - logHealthMetrics(health, logger, config); + logHealthMetrics(health, logger, config, true); const { calculateHealthStatus } = jest.requireMock('./calculate_health_status'); expect(calculateHealthStatus).toBeCalledTimes(1); diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts index 4a2757ee739e9..55c25f6ce3577 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts @@ -27,7 +27,8 @@ export function resetLastLogLevel() { export function logHealthMetrics( monitoredHealth: MonitoredHealth, logger: Logger, - config: TaskManagerConfig + config: TaskManagerConfig, + shouldRunTasks: boolean ) { let logLevel: LogLevel = LogLevel.Debug; const enabled = config.monitored_stats_health_verbose_log.enabled; @@ -38,7 +39,12 @@ export function logHealthMetrics( capacity_estimation: undefined, }, }; - const statusWithoutCapacity = calculateHealthStatus(healthWithoutCapacity, config, logger); + const statusWithoutCapacity = calculateHealthStatus( + healthWithoutCapacity, + config, + shouldRunTasks, + logger + ); if (statusWithoutCapacity === HealthStatus.Warning) { logLevel = LogLevel.Warn; } else if (statusWithoutCapacity === HealthStatus.Error && !isEmpty(monitoredHealth.stats)) { @@ -51,7 +57,9 @@ export function logHealthMetrics( const docLink = `https://www.elastic.co/guide/en/kibana/${docsBranch}/task-manager-health-monitoring.html`; const detectedProblemMessage = `Task Manager detected a degradation in performance. This is usually temporary, and Kibana can recover automatically. If the problem persists, check the docs for troubleshooting information: ${docLink} .`; - if (enabled) { + + // Drift looks at runtime stats which are not available when task manager is not running tasks + if (enabled && shouldRunTasks) { const driftInSeconds = (monitoredHealth.stats.runtime?.value.drift.p99 ?? 0) / 1000; if ( driftInSeconds >= config.monitored_stats_health_verbose_log.warn_delayed_task_start_in_seconds diff --git a/x-pack/plugins/task_manager/server/monitoring/index.ts b/x-pack/plugins/task_manager/server/monitoring/index.ts index 84dd78f432193..de1bea796c038 100644 --- a/x-pack/plugins/task_manager/server/monitoring/index.ts +++ b/x-pack/plugins/task_manager/server/monitoring/index.ts @@ -27,23 +27,23 @@ export { } from './monitoring_stats_stream'; export function createMonitoringStats( - taskPollingLifecycle: TaskPollingLifecycle, - ephemeralTaskLifecycle: EphemeralTaskLifecycle, taskStore: TaskStore, elasticsearchAndSOAvailability$: Observable<boolean>, config: TaskManagerConfig, managedConfig: ManagedConfiguration, - logger: Logger + logger: Logger, + taskPollingLifecycle?: TaskPollingLifecycle, + ephemeralTaskLifecycle?: EphemeralTaskLifecycle ): Observable<MonitoringStats> { return createMonitoringStatsStream( createAggregators( - taskPollingLifecycle, - ephemeralTaskLifecycle, taskStore, elasticsearchAndSOAvailability$, config, managedConfig, - logger + logger, + taskPollingLifecycle, + ephemeralTaskLifecycle ), config ); diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts index 125ce034b0c69..e0e7ddd3e79a4 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.ts @@ -75,17 +75,17 @@ export interface RawMonitoringStats { } export function createAggregators( - taskPollingLifecycle: TaskPollingLifecycle, - ephemeralTaskLifecycle: EphemeralTaskLifecycle, taskStore: TaskStore, elasticsearchAndSOAvailability$: Observable<boolean>, config: TaskManagerConfig, managedConfig: ManagedConfiguration, - logger: Logger + logger: Logger, + taskPollingLifecycle?: TaskPollingLifecycle, + ephemeralTaskLifecycle?: EphemeralTaskLifecycle ): AggregatedStatProvider { const aggregators: AggregatedStatProvider[] = [ createConfigurationAggregator(config, managedConfig), - createTaskRunAggregator(taskPollingLifecycle, config.monitored_stats_running_average_window), + createWorkloadAggregator( taskStore, elasticsearchAndSOAvailability$, @@ -94,7 +94,12 @@ export function createAggregators( logger ), ]; - if (ephemeralTaskLifecycle.enabled) { + if (taskPollingLifecycle) { + aggregators.push( + createTaskRunAggregator(taskPollingLifecycle, config.monitored_stats_running_average_window) + ); + } + if (ephemeralTaskLifecycle && ephemeralTaskLifecycle.enabled) { aggregators.push( createEphemeralTaskAggregator( ephemeralTaskLifecycle, diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index 0a1f77c665044..93e44c4000750 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -11,43 +11,79 @@ import { TaskManagerConfig } from './config'; import { Subject } from 'rxjs'; import { bufferCount, take } from 'rxjs/operators'; import { CoreStatus, ServiceStatusLevels } from '@kbn/core/server'; +import { taskPollingLifecycleMock } from './polling_lifecycle.mock'; +import { TaskPollingLifecycle } from './polling_lifecycle'; +import type { TaskPollingLifecycle as TaskPollingLifecycleClass } from './polling_lifecycle'; +import { ephemeralTaskLifecycleMock } from './ephemeral_task_lifecycle.mock'; +import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; +import type { EphemeralTaskLifecycle as EphemeralTaskLifecycleClass } from './ephemeral_task_lifecycle'; + +let mockTaskPollingLifecycle = taskPollingLifecycleMock.create({}); +jest.mock('./polling_lifecycle', () => { + return { + TaskPollingLifecycle: jest.fn().mockImplementation(() => { + return mockTaskPollingLifecycle; + }), + }; +}); + +let mockEphemeralTaskLifecycle = ephemeralTaskLifecycleMock.create({}); +jest.mock('./ephemeral_task_lifecycle', () => { + return { + EphemeralTaskLifecycle: jest.fn().mockImplementation(() => { + return mockEphemeralTaskLifecycle; + }), + }; +}); + +const coreStart = coreMock.createStart(); +const pluginInitializerContextParams = { + max_workers: 10, + max_attempts: 9, + poll_interval: 3000, + version_conflict_threshold: 80, + max_poll_inactivity_cycles: 10, + request_capacity: 1000, + monitored_aggregated_stats_refresh_rate: 5000, + monitored_stats_health_verbose_log: { + enabled: false, + warn_delayed_task_start_in_seconds: 60, + }, + monitored_stats_required_freshness: 5000, + monitored_stats_running_average_window: 50, + monitored_task_execution_thresholds: { + default: { + error_threshold: 90, + warn_threshold: 80, + }, + custom: {}, + }, + ephemeral_tasks: { + enabled: false, + request_capacity: 10, + }, + unsafe: { + exclude_task_types: [], + }, + event_loop_delay: { + monitor: true, + warn_threshold: 5000, + }, +}; describe('TaskManagerPlugin', () => { + beforeEach(() => { + mockTaskPollingLifecycle = taskPollingLifecycleMock.create({}); + (TaskPollingLifecycle as jest.Mock<TaskPollingLifecycleClass>).mockClear(); + mockEphemeralTaskLifecycle = ephemeralTaskLifecycleMock.create({}); + (EphemeralTaskLifecycle as jest.Mock<EphemeralTaskLifecycleClass>).mockClear(); + }); + describe('setup', () => { test('throws if no valid UUID is available', async () => { - const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - max_workers: 10, - max_attempts: 9, - poll_interval: 3000, - version_conflict_threshold: 80, - max_poll_inactivity_cycles: 10, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_health_verbose_log: { - enabled: false, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_required_freshness: 5000, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: false, - request_capacity: 10, - }, - unsafe: { - exclude_task_types: [], - }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, - }); + const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>( + pluginInitializerContextParams + ); pluginInitializerContext.env.instanceUuid = ''; @@ -60,39 +96,9 @@ describe('TaskManagerPlugin', () => { }); test('throws if setup methods are called after start', async () => { - const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - max_workers: 10, - max_attempts: 9, - poll_interval: 3000, - version_conflict_threshold: 80, - max_poll_inactivity_cycles: 10, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_health_verbose_log: { - enabled: false, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_required_freshness: 5000, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: true, - request_capacity: 10, - }, - unsafe: { - exclude_task_types: [], - }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, - }); + const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>( + pluginInitializerContextParams + ); const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); @@ -135,37 +141,10 @@ describe('TaskManagerPlugin', () => { test('it logs a warning when the unsafe `exclude_task_types` config is used', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>({ - max_workers: 10, - max_attempts: 9, - poll_interval: 3000, - version_conflict_threshold: 80, - max_poll_inactivity_cycles: 10, - request_capacity: 1000, - monitored_aggregated_stats_refresh_rate: 5000, - monitored_stats_health_verbose_log: { - enabled: false, - warn_delayed_task_start_in_seconds: 60, - }, - monitored_stats_required_freshness: 5000, - monitored_stats_running_average_window: 50, - monitored_task_execution_thresholds: { - default: { - error_threshold: 90, - warn_threshold: 80, - }, - custom: {}, - }, - ephemeral_tasks: { - enabled: false, - request_capacity: 10, - }, + ...pluginInitializerContextParams, unsafe: { exclude_task_types: ['*'], }, - event_loop_delay: { - monitor: true, - warn_threshold: 5000, - }, }); const logger = pluginInitializerContext.logger.get(); @@ -178,6 +157,38 @@ describe('TaskManagerPlugin', () => { }); }); + describe('start', () => { + test('should initialize task polling lifecycle if node.roles.backgroundTasks is true', async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>( + pluginInitializerContextParams + ); + pluginInitializerContext.node.roles.backgroundTasks = true; + const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); + taskManagerPlugin.setup(coreMock.createSetup(), { usageCollection: undefined }); + taskManagerPlugin.start(coreStart); + + expect(TaskPollingLifecycle as jest.Mock<TaskPollingLifecycleClass>).toHaveBeenCalledTimes(1); + expect( + EphemeralTaskLifecycle as jest.Mock<EphemeralTaskLifecycleClass> + ).toHaveBeenCalledTimes(1); + }); + + test('should not initialize task polling lifecycle if node.roles.backgroundTasks is false', async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext<TaskManagerConfig>( + pluginInitializerContextParams + ); + pluginInitializerContext.node.roles.backgroundTasks = false; + const taskManagerPlugin = new TaskManagerPlugin(pluginInitializerContext); + taskManagerPlugin.setup(coreMock.createSetup(), { usageCollection: undefined }); + taskManagerPlugin.start(coreStart); + + expect(TaskPollingLifecycle as jest.Mock<TaskPollingLifecycleClass>).not.toHaveBeenCalled(); + expect( + EphemeralTaskLifecycle as jest.Mock<EphemeralTaskLifecycleClass> + ).not.toHaveBeenCalled(); + }); + }); + describe('getElasticsearchAndSOAvailability', () => { test('returns true when both services are available', async () => { const core$ = new Subject<CoreStatus>(); diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index cf7c3368da3d7..357a1d85fffcf 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -28,11 +28,10 @@ import { createManagedConfiguration } from './lib/create_managed_configuration'; import { TaskScheduling } from './task_scheduling'; import { healthRoute } from './routes'; import { createMonitoringStats, MonitoringStats } from './monitoring'; -import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { EphemeralTask } from './task'; +import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { registerTaskManagerUsageCollector } from './usage'; import { TASK_MANAGER_INDEX } from './constants'; - export interface TaskManagerSetupContract { /** * @deprecated @@ -67,6 +66,7 @@ export class TaskManagerPlugin private middleware: Middleware = createInitialMiddleware(); private elasticsearchAndSOAvailability$?: Observable<boolean>; private monitoringStats$ = new Subject<MonitoringStats>(); + private shouldRunBackgroundTasks: boolean; private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; constructor(private readonly initContext: PluginInitializerContext) { @@ -75,6 +75,7 @@ export class TaskManagerPlugin this.config = initContext.config.get<TaskManagerConfig>(); this.definitions = new TaskTypeDictionary(this.logger); this.kibanaVersion = initContext.env.packageInfo.version; + this.shouldRunBackgroundTasks = initContext.node.roles.backgroundTasks; } public setup( @@ -114,6 +115,7 @@ export class TaskManagerPlugin kibanaIndexName: core.savedObjects.getKibanaIndex(), getClusterClient: () => startServicesPromise.then(({ elasticsearch }) => elasticsearch.client), + shouldRunTasks: this.shouldRunBackgroundTasks, }); core.status.derivedStatus$.subscribe((status) => @@ -186,45 +188,47 @@ export class TaskManagerPlugin startingPollInterval: this.config!.poll_interval, }); - this.taskPollingLifecycle = new TaskPollingLifecycle({ - config: this.config!, - definitions: this.definitions, - unusedTypes: REMOVED_TYPES, - logger: this.logger, - executionContext, - taskStore, - usageCounter: this.usageCounter, - middleware: this.middleware, - elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, - ...managedConfiguration, - }); + // Only poll for tasks if configured to run tasks + if (this.shouldRunBackgroundTasks) { + this.taskPollingLifecycle = new TaskPollingLifecycle({ + config: this.config!, + definitions: this.definitions, + unusedTypes: REMOVED_TYPES, + logger: this.logger, + executionContext, + taskStore, + usageCounter: this.usageCounter, + middleware: this.middleware, + elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, + ...managedConfiguration, + }); - this.ephemeralTaskLifecycle = new EphemeralTaskLifecycle({ - config: this.config!, - definitions: this.definitions, - logger: this.logger, - executionContext, - middleware: this.middleware, - elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, - pool: this.taskPollingLifecycle.pool, - lifecycleEvent: this.taskPollingLifecycle.events, - }); + this.ephemeralTaskLifecycle = new EphemeralTaskLifecycle({ + config: this.config!, + definitions: this.definitions, + logger: this.logger, + executionContext, + middleware: this.middleware, + elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, + pool: this.taskPollingLifecycle.pool, + lifecycleEvent: this.taskPollingLifecycle.events, + }); + } createMonitoringStats( - this.taskPollingLifecycle, - this.ephemeralTaskLifecycle, taskStore, this.elasticsearchAndSOAvailability$!, this.config!, managedConfiguration, - this.logger + this.logger, + this.taskPollingLifecycle, + this.ephemeralTaskLifecycle ).subscribe((stat) => this.monitoringStats$.next(stat)); const taskScheduling = new TaskScheduling({ logger: this.logger, taskStore, middleware: this.middleware, - taskPollingLifecycle: this.taskPollingLifecycle, ephemeralTaskLifecycle: this.ephemeralTaskLifecycle, definitions: this.definitions, taskManagerId: taskStore.taskManagerId, @@ -240,7 +244,8 @@ export class TaskManagerPlugin runSoon: (...args) => taskScheduling.runSoon(...args), bulkUpdateSchedules: (...args) => taskScheduling.bulkUpdateSchedules(...args), ephemeralRunNow: (task: EphemeralTask) => taskScheduling.ephemeralRunNow(task), - supportsEphemeralTasks: () => this.config.ephemeral_tasks.enabled, + supportsEphemeralTasks: () => + this.config.ephemeral_tasks.enabled && this.shouldRunBackgroundTasks, }; } diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts index 31d4a17434598..f2d4dae1532a4 100644 --- a/x-pack/plugins/task_manager/server/routes/health.test.ts +++ b/x-pack/plugins/task_manager/server/routes/health.test.ts @@ -64,6 +64,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); const [config] = router.get.mock.calls[0]; @@ -86,6 +87,7 @@ describe('healthRoute', () => { kibanaIndexName: 'foo', getClusterClient: () => Promise.resolve(mockClusterClient), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); const [, handler] = router.get.mock.calls[0]; @@ -126,6 +128,7 @@ describe('healthRoute', () => { kibanaIndexName: 'foo', getClusterClient: () => Promise.resolve(mockClusterClient), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); const [, handler] = router.get.mock.calls[0]; @@ -171,6 +174,7 @@ describe('healthRoute', () => { kibanaVersion: '8.0', kibanaIndexName: 'foo', getClusterClient: () => Promise.resolve(mockClusterClient), + shouldRunTasks: true, }); const [, handler] = router.get.mock.calls[0]; @@ -212,6 +216,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); stats$.next(mockStat); @@ -270,6 +275,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); stats$.next(warnRuntimeStat); @@ -346,6 +352,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); stats$.next(errorRuntimeStat); @@ -409,6 +416,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); const serviceStatus = getLatest(serviceStatus$); @@ -490,6 +498,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); await sleep(0); @@ -563,6 +572,7 @@ describe('healthRoute', () => { kibanaIndexName: '.kibana', getClusterClient: () => Promise.resolve(elasticsearchServiceMock.createClusterClient()), usageCounter: mockUsageCounter, + shouldRunTasks: true, }); await sleep(0); diff --git a/x-pack/plugins/task_manager/server/routes/health.ts b/x-pack/plugins/task_manager/server/routes/health.ts index 03fe93c79c951..ba9eaa23c6678 100644 --- a/x-pack/plugins/task_manager/server/routes/health.ts +++ b/x-pack/plugins/task_manager/server/routes/health.ts @@ -57,6 +57,7 @@ export interface HealthRouteParams { config: TaskManagerConfig; kibanaVersion: string; kibanaIndexName: string; + shouldRunTasks: boolean; getClusterClient: () => Promise<IClusterClient>; usageCounter?: UsageCounter; } @@ -75,6 +76,7 @@ export function healthRoute(params: HealthRouteParams): { kibanaIndexName, getClusterClient, usageCounter, + shouldRunTasks, } = params; // if "hot" health stats are any more stale than monitored_stats_required_freshness (pollInterval +1s buffer by default) @@ -83,7 +85,7 @@ export function healthRoute(params: HealthRouteParams): { function getHealthStatus(monitoredStats: MonitoringStats) { const summarizedStats = summarizeMonitoringStats(logger, monitoredStats, config); - const status = calculateHealthStatus(summarizedStats, config, logger); + const status = calculateHealthStatus(summarizedStats, config, shouldRunTasks, logger); const now = Date.now(); const timestamp = new Date(now).toISOString(); return { id: taskManagerId, timestamp, status, ...summarizedStats }; @@ -109,7 +111,7 @@ export function healthRoute(params: HealthRouteParams): { .subscribe(([monitoredHealth, serviceStatus]) => { serviceStatus$.next(serviceStatus); monitoredHealth$.next(monitoredHealth); - logHealthMetrics(monitoredHealth, logger, config); + logHealthMetrics(monitoredHealth, logger, config, shouldRunTasks); }); router.get( diff --git a/x-pack/plugins/task_manager/server/task_scheduling.test.ts b/x-pack/plugins/task_manager/server/task_scheduling.test.ts index ca0d8dc0cadd9..7c9d6e9bd5fbe 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.test.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.test.ts @@ -10,7 +10,6 @@ import moment from 'moment'; import { asTaskRunEvent, TaskPersistence } from './task_events'; import { TaskLifecycleEvent } from './polling_lifecycle'; -import { taskPollingLifecycleMock } from './polling_lifecycle.mock'; import { TaskScheduling } from './task_scheduling'; import { asErr, asOk } from './lib/result_type'; import { ConcreteTaskInstance, TaskStatus } from './task'; @@ -35,11 +34,9 @@ jest.mock('elastic-apm-node', () => ({ describe('TaskScheduling', () => { const mockTaskStore = taskStoreMock.create({}); - const mockTaskManager = taskPollingLifecycleMock.create({}); const definitions = new TaskTypeDictionary(mockLogger()); const taskSchedulingOpts = { taskStore: mockTaskStore, - taskPollingLifecycle: mockTaskManager, logger: mockLogger(), middleware: createInitialMiddleware(), definitions, @@ -476,6 +473,28 @@ describe('TaskScheduling', () => { `[Error: Ephemeral Task of type foo was rejected]` ); }); + + test('rejects ephemeral task if ephemeralTaskLifecycle is not defined', async () => { + const ephemeralTask = mockTask({ + state: { + foo: 'bar', + }, + }); + const middleware = createInitialMiddleware(); + middleware.beforeSave = jest.fn().mockImplementation(async () => { + return { taskInstance: ephemeralTask }; + }); + const taskScheduling = new TaskScheduling({ + ...taskSchedulingOpts, + middleware, + ephemeralTaskLifecycle: undefined, + }); + + const result = taskScheduling.ephemeralRunNow(ephemeralTask); + expect(result).rejects.toMatchInlineSnapshot( + `[Error: Ephemeral Task of type foo was rejected because ephemeral tasks are not supported]` + ); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/task_scheduling.ts b/x-pack/plugins/task_manager/server/task_scheduling.ts index b604b683570ae..ef6ab359b9064 100644 --- a/x-pack/plugins/task_manager/server/task_scheduling.ts +++ b/x-pack/plugins/task_manager/server/task_scheduling.ts @@ -12,7 +12,7 @@ import { getOrElse, isSome, map as mapOptional, Option } from 'fp-ts/lib/Option' import uuid from 'uuid'; import { chunk, pick } from 'lodash'; -import { merge, Subject } from 'rxjs'; +import { Subject } from 'rxjs'; import agent from 'elastic-apm-node'; import { Logger } from '@kbn/core/server'; import { mustBeAllOf } from './queries/query_clauses'; @@ -42,7 +42,7 @@ import { } from './task'; import { TaskStore } from './task_store'; import { ensureDeprecatedFieldsAreCorrected } from './lib/correct_deprecated_fields'; -import { TaskLifecycleEvent, TaskPollingLifecycle } from './polling_lifecycle'; +import { TaskLifecycleEvent } from './polling_lifecycle'; import { TaskTypeDictionary } from './task_type_dictionary'; import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { EphemeralTaskRejectedDueToCapacityError } from './task_running'; @@ -52,8 +52,7 @@ const VERSION_CONFLICT_STATUS = 409; export interface TaskSchedulingOpts { logger: Logger; taskStore: TaskStore; - taskPollingLifecycle: TaskPollingLifecycle; - ephemeralTaskLifecycle: EphemeralTaskLifecycle; + ephemeralTaskLifecycle?: EphemeralTaskLifecycle; middleware: Middleware; definitions: TaskTypeDictionary; taskManagerId: string; @@ -84,8 +83,7 @@ export interface RunNowResult { export class TaskScheduling { private store: TaskStore; - private taskPollingLifecycle: TaskPollingLifecycle; - private ephemeralTaskLifecycle: EphemeralTaskLifecycle; + private ephemeralTaskLifecycle?: EphemeralTaskLifecycle; private logger: Logger; private middleware: Middleware; private definitions: TaskTypeDictionary; @@ -99,7 +97,6 @@ export class TaskScheduling { constructor(opts: TaskSchedulingOpts) { this.logger = opts.logger; this.middleware = opts.middleware; - this.taskPollingLifecycle = opts.taskPollingLifecycle; this.ephemeralTaskLifecycle = opts.ephemeralTaskLifecycle; this.store = opts.taskStore; this.definitions = opts.definitions; @@ -239,6 +236,12 @@ export class TaskScheduling { task: EphemeralTask, options?: Record<string, unknown> ): Promise<RunNowResult> { + if (!this.ephemeralTaskLifecycle) { + throw new EphemeralTaskRejectedDueToCapacityError( + `Ephemeral Task of type ${task.taskType} was rejected because ephemeral tasks are not supported`, + task + ); + } const id = uuid.v4(); const { taskInstance: modifiedTask } = await this.middleware.beforeSave({ ...options, @@ -261,7 +264,7 @@ export class TaskScheduling { .catch((err: Error) => { reject(err); }); - const attemptToRunResult = this.ephemeralTaskLifecycle.attemptToRun({ + const attemptToRunResult = this.ephemeralTaskLifecycle!.attemptToRun({ id, scheduledAt: new Date(), runAt: new Date(), @@ -307,64 +310,68 @@ export class TaskScheduling { private awaitTaskRunResult(taskId: string, cancel?: Promise<void>): Promise<RunNowResult> { return new Promise((resolve, reject) => { + if (!this.ephemeralTaskLifecycle) { + reject( + new Error( + `Failed to run task "${taskId}" because ephemeral tasks are not supported. Rescheduled the task to ensure it is picked up as soon as possible.` + ) + ); + } // listen for all events related to the current task - const subscription = merge( - this.taskPollingLifecycle.events, - this.ephemeralTaskLifecycle.events - ) - .pipe(filter(({ id }: TaskLifecycleEvent) => id === taskId)) - .subscribe((taskEvent: TaskLifecycleEvent) => { - if (isTaskClaimEvent(taskEvent)) { - mapErr(async (error: ClaimTaskErr) => { - // reject if any error event takes place for the requested task - subscription.unsubscribe(); - if ( - isSome(error.task) && - error.errorType === TaskClaimErrorType.CLAIMED_BY_ID_OUT_OF_CAPACITY - ) { - const task = error.task.value; - const definition = this.definitions.get(task.taskType); - return reject( - new Error( - `Failed to run task "${taskId}" as we would exceed the max concurrency of "${ - definition?.title ?? task.taskType - }" which is ${ - definition?.maxConcurrency - }. Rescheduled the task to ensure it is picked up as soon as possible.` - ) - ); - } else { - return reject(await this.identifyTaskFailureReason(taskId, error.task)); - } - }, taskEvent.event); - } else { - either<OkResultOf<TaskLifecycleEvent>, ErrResultOf<TaskLifecycleEvent>>( - taskEvent.event, - (taskInstance: OkResultOf<TaskLifecycleEvent>) => { - // resolve if the task has run sucessfully - if (isTaskRunEvent(taskEvent)) { - subscription.unsubscribe(); - resolve(pick((taskInstance as RanTask).task, ['id', 'state'])); - } - }, - async (errorResult: ErrResultOf<TaskLifecycleEvent>) => { - // reject if any error event takes place for the requested task + const subscription = this.ephemeralTaskLifecycle!.events.pipe( + filter(({ id }: TaskLifecycleEvent) => id === taskId) + ).subscribe((taskEvent: TaskLifecycleEvent) => { + if (isTaskClaimEvent(taskEvent)) { + mapErr(async (error: ClaimTaskErr) => { + // reject if any error event takes place for the requested task + subscription.unsubscribe(); + if ( + isSome(error.task) && + error.errorType === TaskClaimErrorType.CLAIMED_BY_ID_OUT_OF_CAPACITY + ) { + const task = error.task.value; + const definition = this.definitions.get(task.taskType); + return reject( + new Error( + `Failed to run task "${taskId}" as we would exceed the max concurrency of "${ + definition?.title ?? task.taskType + }" which is ${ + definition?.maxConcurrency + }. Rescheduled the task to ensure it is picked up as soon as possible.` + ) + ); + } else { + return reject(await this.identifyTaskFailureReason(taskId, error.task)); + } + }, taskEvent.event); + } else { + either<OkResultOf<TaskLifecycleEvent>, ErrResultOf<TaskLifecycleEvent>>( + taskEvent.event, + (taskInstance: OkResultOf<TaskLifecycleEvent>) => { + // resolve if the task has run sucessfully + if (isTaskRunEvent(taskEvent)) { subscription.unsubscribe(); - return reject( - new Error( - `Failed to run task "${taskId}": ${ - isTaskRunRequestEvent(taskEvent) - ? `Task Manager is at capacity, please try again later` - : isTaskRunEvent(taskEvent) - ? `${(errorResult as ErroredTask).error}` - : `${errorResult}` - }` - ) - ); + resolve(pick((taskInstance as RanTask).task, ['id', 'state'])); } - ); - } - }); + }, + async (errorResult: ErrResultOf<TaskLifecycleEvent>) => { + // reject if any error event takes place for the requested task + subscription.unsubscribe(); + return reject( + new Error( + `Failed to run task "${taskId}": ${ + isTaskRunRequestEvent(taskEvent) + ? `Task Manager is at capacity, please try again later` + : isTaskRunEvent(taskEvent) + ? `${(errorResult as ErroredTask).error}` + : `${errorResult}` + }` + ) + ); + } + ); + } + }); if (cancel) { cancel.then(() => { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 141eed06e7ef8..b9ea4ea54a7f1 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -776,7 +776,7 @@ export const StepDetailsForm: FC<StepDetailsFormProps> = React.memo( } helpText={i18n.translate('xpack.transform.stepDetailsForm.frequencyHelpText', { defaultMessage: - 'The interval between checks for changes in the source indices when the transform is running continuously. Also determines the retry interval in the event of transient failures while the transform is searching or indexing. The minimum value is 1s and the maximum is 1h.', + 'The interval to check for changes in source indices when the transformation runs continuously.', })} > <EuiFieldText @@ -814,7 +814,7 @@ export const StepDetailsForm: FC<StepDetailsFormProps> = React.memo( 'xpack.transform.stepDetailsForm.maxPageSearchSizeHelpText', { defaultMessage: - 'Defines the initial page size to use for the composite aggregation for each checkpoint.', + 'The initial page size to use for the composite aggregation for each checkpoint.', } )} > diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx index f5e46a60ebb68..ff517942101b8 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/edit_transform_flyout_form.tsx @@ -110,7 +110,7 @@ export const EditTransformFlyoutForm: FC<EditTransformFlyoutFormProps> = ({ errorMessages={formFields.frequency.errorMessages} helpText={i18n.translate('xpack.transform.transformList.editFlyoutFormFrequencyHelpText', { defaultMessage: - 'The interval between checks for changes in the source indices when the transform is running continuously. Also determines the retry interval in the event of transient failures while the transform is searching or indexing. The minimum value is 1s and the maximum is 1h.', + 'The interval to check for changes in source indices when the transformation runs continuously.', })} label={i18n.translate('xpack.transform.transformList.editFlyoutFormFrequencyLabel', { defaultMessage: 'Frequency', @@ -346,7 +346,7 @@ export const EditTransformFlyoutForm: FC<EditTransformFlyoutFormProps> = ({ 'xpack.transform.transformList.editFlyoutFormMaxPageSearchSizeHelptext', { defaultMessage: - 'Defines the initial page size to use for the composite aggregation for each checkpoint.', + 'The initial page size to use for the composite aggregation for each checkpoint.', } )} label={i18n.translate( diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 6ae2f6d5fa63b..37123f50312c7 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -3888,25 +3888,6 @@ "home.sampleData.logsSpec.webTrafficTitle": "[Logs] Trafic Internet", "home.sampleData.logsSpecDescription": "Exemple de données, de visualisations et de tableaux de bord pour le monitoring des logs Internet.", "home.sampleData.logsSpecTitle": "Exemple de logs Internet", - "homePackages.sampleDataSet.installedLabel": "{name} installé", - "homePackages.sampleDataSet.unableToInstallErrorMessage": "Impossible d'installer l'exemple d’ensemble de données : {name}.", - "homePackages.sampleDataSet.unableToLoadListErrorMessage": "Impossible de charger la liste des exemples d’ensemble de données", - "homePackages.sampleDataSet.unableToUninstallErrorMessage": "Impossible de désinstaller l'exemple d’ensemble de données : {name}.", - "homePackages.sampleDataSet.uninstalledLabel": "{name} désinstallé", - "homePackages.sampleDataCard.addButtonAriaLabel": "Ajouter {datasetName}", - "homePackages.sampleDataCard.addButtonLabel": "Ajouter des données", - "homePackages.sampleDataCard.addingButtonAriaLabel": "Ajout de {datasetName}", - "homePackages.sampleDataCard.addingButtonLabel": "Ajout", - "homePackages.sampleDataCard.dashboardLinkLabel": "Tableau de bord", - "homePackages.sampleDataCard.default.addButtonAriaLabel": "Ajouter {datasetName}", - "homePackages.sampleDataCard.default.addButtonLabel": "Ajouter des données", - "homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "Impossible de vérifier le statut de l'ensemble de données. Erreur : {statusMsg}.", - "homePackages.sampleDataCard.removeButtonAriaLabel": "Supprimer {datasetName}", - "homePackages.sampleDataCard.removeButtonLabel": "Supprimer", - "homePackages.sampleDataCard.removingButtonAriaLabel": "Suppression de {datasetName}", - "homePackages.sampleDataCard.removingButtonLabel": "Suppression", - "homePackages.sampleDataCard.viewDataButtonAriaLabel": "Consulter {datasetName}", - "homePackages.sampleDataCard.viewDataButtonLabel": "Consulter les données", "home.solutionsSection.sectionTitle": "Choisir votre solution", "home.tryButtonLabel": "Ajouter des intégrations", "home.tutorial.addDataToKibanaDescription": "En plus d'ajouter {integrationsLink}, vous pouvez essayer l'exemple de données ou charger vos propres données.", @@ -4655,6 +4636,25 @@ "home.tutorials.zscalerLogs.nameTitle": "Logs Zscaler", "home.tutorials.zscalerLogs.shortDescription": "Collectez et analysez les logs à partir de Zscaler NSS avec Filebeat.", "home.welcomeTitle": "Bienvenue dans Elastic", + "homePackages.sampleDataSet.installedLabel": "{name} installé", + "homePackages.sampleDataSet.unableToInstallErrorMessage": "Impossible d'installer l'exemple d’ensemble de données : {name}.", + "homePackages.sampleDataSet.unableToLoadListErrorMessage": "Impossible de charger la liste des exemples d’ensemble de données", + "homePackages.sampleDataSet.unableToUninstallErrorMessage": "Impossible de désinstaller l'exemple d’ensemble de données : {name}.", + "homePackages.sampleDataSet.uninstalledLabel": "{name} désinstallé", + "homePackages.sampleDataCard.addButtonAriaLabel": "Ajouter {datasetName}", + "homePackages.sampleDataCard.addButtonLabel": "Ajouter des données", + "homePackages.sampleDataCard.addingButtonAriaLabel": "Ajout de {datasetName}", + "homePackages.sampleDataCard.addingButtonLabel": "Ajout", + "homePackages.sampleDataCard.dashboardLinkLabel": "Tableau de bord", + "homePackages.sampleDataCard.default.addButtonAriaLabel": "Ajouter {datasetName}", + "homePackages.sampleDataCard.default.addButtonLabel": "Ajouter des données", + "homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "Impossible de vérifier le statut de l'ensemble de données. Erreur : {statusMsg}.", + "homePackages.sampleDataCard.removeButtonAriaLabel": "Supprimer {datasetName}", + "homePackages.sampleDataCard.removeButtonLabel": "Supprimer", + "homePackages.sampleDataCard.removingButtonAriaLabel": "Suppression de {datasetName}", + "homePackages.sampleDataCard.removingButtonLabel": "Suppression", + "homePackages.sampleDataCard.viewDataButtonAriaLabel": "Consulter {datasetName}", + "homePackages.sampleDataCard.viewDataButtonLabel": "Consulter les données", "indexPatternEditor.aliasLabel": "Alias", "indexPatternEditor.createIndex.noMatch": "Le nom doit correspondre à au moins un flux de données, index ou alias d'index.", "indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel": "--- Je ne souhaite pas utiliser le filtre temporel ---", @@ -7823,7 +7823,6 @@ "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText": "Entrez la {versionLink} de l'agent Java Elastic APM qui doit être attachée.", "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText.version": "version", "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.invalid": "Version non valide", - "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.placeHolder": "Ajouter une version", "xpack.apm.fleetIntegration.assets.description": "Consulter les traces de l'application et les cartes de service dans APM", "xpack.apm.fleetIntegration.assets.name": "Services", "xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText": "Installer l'agent APM", @@ -11645,9 +11644,7 @@ "xpack.enterpriseSearch.appSearch.tokens.update": "La clé d'API \"{name}\" a été mise à jour", "xpack.enterpriseSearch.content.description": "Enterprise Search offre un certain nombre de moyens de rendre vos données facilement interrogeables. Vous pouvez choisir entre le robot d'indexation, les indices Elasticsearch, l'API, les téléchargements directs ou les connecteurs tiers.", "xpack.enterpriseSearch.content.nav.appSearchTitle": "App Search", - "xpack.enterpriseSearch.content.nav.connectorSettingsTitle": "Paramètres du connecteur", "xpack.enterpriseSearch.content.nav.contentTitle": "Contenu", - "xpack.enterpriseSearch.content.nav.crawlerSettingsTitle": "Paramètres du robot d'indexation", "xpack.enterpriseSearch.content.nav.enterpriseSearchOverviewTitle": "Aperçu", "xpack.enterpriseSearch.content.nav.searchIndicesTitle": "Rechercher dans les index", "xpack.enterpriseSearch.content.nav.workplaceSearchTitle": "Workplace Search", @@ -11767,11 +11764,6 @@ "xpack.enterpriseSearch.overview.gettingStartedSteps.addData.title": "Ajouter vos documents et vos données dans Enterprise Search", "xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.message": "Vous pouvez utiliser les moteurs de recherche pour créer des expériences de recherche personnalisées pour vos clients ou vos équipes internes avec App Search ou Workplace Search. Sinon, vous pouvez utiliser Search UI pour vous connecter directement à un index Elasticsearch pour créer des expériences de recherche côté client pour vos utilisateurs.", "xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.title": "Créer une expérience de recherche", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.description": "Toute la puissance d'Elasticsearch, sans les contraintes d'apprentissage.", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.title": "Créer un moteur App Search", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createASearchEngineButton": "Créer un moteur de recherche", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.description": "Expérience de recherche sécurisée pour les équipes internes", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.title": "Créer un groupe Workplace Search", "xpack.enterpriseSearch.overview.gettingStartedSteps.searchWithElasticsearchLink": "Rechercher avec l'API Elasticsearch", "xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message": "Plonger dans les analyses et ajuster les paramètres de recherche pour aider vos utilisateurs à trouver exactement ce qu'ils cherchent", "xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title": "Ajuster votre pertinence de recherche", @@ -11804,13 +11796,6 @@ "xpack.enterpriseSearch.overview.setupCta.description": "Ajoutez des fonctions de recherche à votre application ou à votre organisation interne avec Elastic App Search et Workplace Search. Regardez la vidéo pour savoir ce qu'il est possible de faire lorsque la recherche est facilitée.", "xpack.enterpriseSearch.overview.setupHeading": "Choisissez un produit à configurer et lancez-vous.", "xpack.enterpriseSearch.overview.subheading": "Ajoutez une fonction de recherche à votre application ou à votre organisation.", - "xpack.enterpriseSearch.overviewContent.heading": "Bienvenue dans Enterprise Search", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsBody": "Vous n'avez pas l'autorisation de consulter cette page. Si vous pensez qu'il s'agit d'une erreur, veuillez contacter votre administrateur.", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsButtonLabel": "Rendez-vous sur le tableau de bord Kibana", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterBody": "Rendez-vous sur le tableau de bord Kibana", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterLinkLabel": "Lire la documentation", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsIllustration": "Illustration de permissions insuffisantes", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsTitle": "Permissions insuffisantes", "xpack.enterpriseSearch.productSelectorCalloutTitle": "Des fonctionnalités de niveau entreprise pour les grandes et petites équipes", "xpack.enterpriseSearch.readOnlyMode.warning": "Enterprise Search est en mode de lecture seule. Vous ne pourrez pas effectuer de changements tels que création, modification ou suppression.", "xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "Ajouter un mapping", @@ -22397,16 +22382,10 @@ "xpack.observability.resources.quick_start": "Vidéos de démarrage rapide", "xpack.observability.resources.title": "Ressources", "xpack.observability.resources.training": "Cours gratuit Observability", - "xpack.observability.ruleDetails.actions": "Actions", "xpack.observability.ruleDetails.alerts": "Alertes", "xpack.observability.ruleDetails.byWord": "par", - "xpack.observability.ruleDetails.conditions": "condition{s}", - "xpack.observability.ruleDetails.conditionsTitle": "Conditions", - "xpack.observability.ruleDetails.connectorsLoadError": "Impossible de charger les connecteurs d'actions de règles. Raison : {message}", "xpack.observability.ruleDetails.createdWord": "Créé", - "xpack.observability.ruleDetails.definition": "Définition", "xpack.observability.ruleDetails.deleteRule": "Supprimer la règle", - "xpack.observability.ruleDetails.description": "Description", "xpack.observability.ruleDetails.editRule": "Modifier la règle", "xpack.observability.ruleDetails.errorPromptBody": "Une erreur s'est produite lors du chargement des détails de la règle.", "xpack.observability.ruleDetails.errorPromptTitle": "Impossible de charger les détails de la règle", @@ -22415,15 +22394,11 @@ "xpack.observability.ruleDetails.last24h": "(dernières 24 h)", "xpack.observability.ruleDetails.lastRun": "Dernière exécution", "xpack.observability.ruleDetails.lastUpdatedMessage": "Dernière mise à jour", - "xpack.observability.ruleDetails.noActions": "Aucune action", - "xpack.observability.ruleDetails.notifyWhen": "Notifier", "xpack.observability.ruleDetails.onWord": "le", "xpack.observability.ruleDetails.rule.alertsTabText": "Alertes", "xpack.observability.ruleDetails.rule.eventLogTabText": "Historique d'exécution", "xpack.observability.ruleDetails.ruleIs": "La règle est", "xpack.observability.ruleDetails.ruleLoadError": "Impossible de charger la règle. Raison : {message}", - "xpack.observability.ruleDetails.ruleType": "Type de règle", - "xpack.observability.ruleDetails.runsEvery": "S'exécute toutes les", "xpack.observability.ruleDetails.tagsTitle": "Balises", "xpack.observability.ruleDetails.triggreAction.status": "Statut", "xpack.observability.rules.addRuleButtonLabel": "Créer une règle", @@ -24578,7 +24553,6 @@ "xpack.securitySolution.console.builtInCommands.help.helpTitle": "Commandes disponibles", "xpack.securitySolution.console.builtInCommands.helpAbout": "Afficher la liste des commandes disponibles", "xpack.securitySolution.console.commandList.footerText": "Pour plus d'informations sur les commandes ci-dessus, utilisez l'argument {helpOption}. Exemple : {cmdExample}", - "xpack.securitySolution.console.commandUsage.atLeastOneOptionRequiredMessage": "Remarque : au moins une option doit être utilisée.", "xpack.securitySolution.console.commandUsage.inputUsage": "Utilisation :", "xpack.securitySolution.console.commandValidation.argSupportedOnlyOnce": "cet argument ne peut être utilisé qu’une fois : {argName}.", "xpack.securitySolution.console.commandValidation.invalidArgValue": "valeur d'argument non valide : {argName}. {error}", @@ -26389,7 +26363,6 @@ "xpack.securitySolution.endpointManagemnet.noPermissionsText": "Vous ne disposez pas des autorisations Kibana requises pour utiliser Elastic Security Administration", "xpack.securitySolution.endpointPolicyStatus.revisionNumber": "rév. {revNumber}", "xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "Politique appliquée", - "xpack.securitySolution.endpointResponseActions.isolate.errorMessage": "Échec de l'action d'isolation avec : {errors}", "xpack.securitySolution.endpointResponseActions.isolate.errorMessageTitle": "Échec", "xpack.securitySolution.endpointResponseActions.isolate.successMessageTitle": "Réussite", "xpack.securitySolution.endpointResponseActions.status.agentStatus": "Statut de l'agent", @@ -28924,15 +28897,10 @@ "xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "L'expression contient des erreurs.", "xpack.stackAlerts.esQuery.ui.alertType.defaultActionMessage": "L'alerte de recherche Elasticsearch \"\\{\\{alertName\\}\\}\" est active :\n\n- Valeur : \\{\\{context.value\\}\\}\n- Conditions remplies : \\{\\{context.conditions\\}\\} sur \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\}\n- Horodatage : \\{\\{context.date\\}\\}\n- Lien : \\{\\{context.link\\}\\}", "xpack.stackAlerts.esQuery.ui.alertType.descriptionText": "Alerte lorsque des correspondances sont trouvées au cours de la dernière exécution de la requête.", - "xpack.stackAlerts.esQuery.ui.conditionPrompt": "Lorsque le nombre de correspondances", - "xpack.stackAlerts.esQuery.ui.conditionPrompt.toolTip": "La fenêtre temporelle définie ci-dessous s'applique uniquement à la première vérification de règle.", "xpack.stackAlerts.esQuery.ui.numQueryMatchesText": "La recherche correspond à {count} documents dans le/la/les dernier(s)/dernière(s) {window}.", "xpack.stackAlerts.esQuery.ui.queryEditor": "Éditeur de recherche Elasticsearch", "xpack.stackAlerts.esQuery.ui.queryError": "Erreur lors du test de la recherche : {message}", - "xpack.stackAlerts.esQuery.ui.queryPrompt": "Définir la recherche Elasticsearch", "xpack.stackAlerts.esQuery.ui.queryPrompt.help": "Documentation DSL sur la recherche Elasticsearch", - "xpack.stackAlerts.esQuery.ui.queryPrompt.label": "Recherche Elasticsearch", - "xpack.stackAlerts.esQuery.ui.selectIndex": "Sélectionner un index et une taille", "xpack.stackAlerts.esQuery.ui.sizeExpression": "Taille", "xpack.stackAlerts.esQuery.ui.testQuery": "Tester la recherche", "xpack.stackAlerts.esQuery.ui.testQueryIsExecuted": "La requête a été exécutée.", @@ -29007,11 +28975,6 @@ "xpack.stackAlerts.indexThreshold.alertTypeTitle": "Seuil de l'index", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "thresholdComparator spécifié non valide : {comparator}", "xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold] : requiert deux éléments pour le comparateur \"{thresholdComparator}\"", - "xpack.stackAlerts.searchSource.ui.conditionPrompt": "Lorsque le nombre de correspondances", - "xpack.stackAlerts.searchSource.ui.searchQuery": "Requête de recherche", - "xpack.stackAlerts.searchSource.ui.selectSizePrompt": "Sélectionner une taille", - "xpack.stackAlerts.searchSource.ui.sizeExpression": "Taille", - "xpack.stackAlerts.searchThreshold.ui.conditionPrompt": "Lorsque le nombre de documents correspond à", "xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "L'expression contient des erreurs.", "xpack.stackAlerts.threshold.ui.alertType.defaultActionMessage": "l'alerte \"\\{\\{alertName\\}\\}\" est active pour le groupe \"\\{\\{context.group\\}\\}\" :\n\n- Valeur : \\{\\{context.value\\}\\}\n- Conditions remplies : \\{\\{context.conditions\\}\\} sur \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\}\n- Horodatage : \\{\\{context.date\\}\\}", "xpack.stackAlerts.threshold.ui.alertType.descriptionText": "Alerte lorsqu'une recherche agrégée atteint le seuil.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c72d2d347e76d..837f99df5744f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3886,25 +3886,6 @@ "home.sampleData.logsSpec.webTrafficTitle": "[ログ] Web トラフィック", "home.sampleData.logsSpecDescription": "Web ログを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", "home.sampleData.logsSpecTitle": "サンプル Web ログ", - "homePackages.sampleDataSet.installedLabel": "{name} がインストールされました", - "homePackages.sampleDataSet.unableToInstallErrorMessage": "サンプルデータセット「{name}」をインストールできません", - "homePackages.sampleDataSet.unableToLoadListErrorMessage": "サンプルデータセットのリストを読み込めません", - "homePackages.sampleDataSet.unableToUninstallErrorMessage": "サンプルデータセット「{name}」をアンインストールできません", - "homePackages.sampleDataSet.uninstalledLabel": "{name} がアンインストールされました", - "homePackages.sampleDataCard.addButtonAriaLabel": "{datasetName} を追加", - "homePackages.sampleDataCard.addButtonLabel": "データの追加", - "homePackages.sampleDataCard.addingButtonAriaLabel": "{datasetName} を追加", - "homePackages.sampleDataCard.addingButtonLabel": "追加中", - "homePackages.sampleDataCard.dashboardLinkLabel": "ダッシュボード", - "homePackages.sampleDataCard.default.addButtonAriaLabel": "{datasetName} を追加", - "homePackages.sampleDataCard.default.addButtonLabel": "データの追加", - "homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "データセットステータスを確認できません、エラー:{statusMsg}", - "homePackages.sampleDataCard.removeButtonAriaLabel": "{datasetName} を削除", - "homePackages.sampleDataCard.removeButtonLabel": "削除", - "homePackages.sampleDataCard.removingButtonAriaLabel": "{datasetName} を削除中", - "homePackages.sampleDataCard.removingButtonLabel": "削除中", - "homePackages.sampleDataCard.viewDataButtonAriaLabel": "{datasetName} を表示", - "homePackages.sampleDataCard.viewDataButtonLabel": "データを表示", "home.solutionsSection.sectionTitle": "ソリューションを選択", "home.tryButtonLabel": "統合の追加", "home.tutorial.addDataToKibanaDescription": "{integrationsLink}を追加するほかに、サンプルデータを試したり、独自のデータをアップロードしたりできます。", @@ -4652,6 +4633,25 @@ "home.tutorials.zscalerLogs.nameTitle": "Zscaler ログ", "home.tutorials.zscalerLogs.shortDescription": "Filebeatを使用してZscaler NSSからログを収集して解析します。", "home.welcomeTitle": "Elasticへようこそ", + "homePackages.sampleDataSet.installedLabel": "{name} がインストールされました", + "homePackages.sampleDataSet.unableToInstallErrorMessage": "サンプルデータセット「{name}」をインストールできません", + "homePackages.sampleDataSet.unableToLoadListErrorMessage": "サンプルデータセットのリストを読み込めません", + "homePackages.sampleDataSet.unableToUninstallErrorMessage": "サンプルデータセット「{name}」をアンインストールできません", + "homePackages.sampleDataSet.uninstalledLabel": "{name} がアンインストールされました", + "homePackages.sampleDataCard.addButtonAriaLabel": "{datasetName} を追加", + "homePackages.sampleDataCard.addButtonLabel": "データの追加", + "homePackages.sampleDataCard.addingButtonAriaLabel": "{datasetName} を追加", + "homePackages.sampleDataCard.addingButtonLabel": "追加中", + "homePackages.sampleDataCard.dashboardLinkLabel": "ダッシュボード", + "homePackages.sampleDataCard.default.addButtonAriaLabel": "{datasetName} を追加", + "homePackages.sampleDataCard.default.addButtonLabel": "データの追加", + "homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "データセットステータスを確認できません、エラー:{statusMsg}", + "homePackages.sampleDataCard.removeButtonAriaLabel": "{datasetName} を削除", + "homePackages.sampleDataCard.removeButtonLabel": "削除", + "homePackages.sampleDataCard.removingButtonAriaLabel": "{datasetName} を削除中", + "homePackages.sampleDataCard.removingButtonLabel": "削除中", + "homePackages.sampleDataCard.viewDataButtonAriaLabel": "{datasetName} を表示", + "homePackages.sampleDataCard.viewDataButtonLabel": "データを表示", "indexPatternEditor.aliasLabel": "エイリアス", "indexPatternEditor.createIndex.noMatch": "名前は1つ以上のデータストリーム、インデックス、またはインデックスエイリアスと一致する必要があります。", "indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel": "--- 時間フィルターを使用しない ---", @@ -7817,7 +7817,6 @@ "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText": "関連付けるElastic APM Javaエージェントの{versionLink}を入力します。", "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText.version": "バージョン", "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.invalid": "無効なバージョン", - "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.placeHolder": "バージョンの追加", "xpack.apm.fleetIntegration.assets.description": "APMでアプリケーショントレースとサービスマップを表示", "xpack.apm.fleetIntegration.assets.name": "サービス", "xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText": "APMエージェントのインストール", @@ -11636,9 +11635,7 @@ "xpack.enterpriseSearch.appSearch.tokens.update": "APIキー'{name}'が更新されました", "xpack.enterpriseSearch.content.description": "エンタープライズ サーチでは、さまざまな方法で簡単にデータを検索可能にできます。Webクローラー、Elasticsearchインデックス、API、直接アップロード、サードパーティコネクターから選択します。", "xpack.enterpriseSearch.content.nav.appSearchTitle": "App Search", - "xpack.enterpriseSearch.content.nav.connectorSettingsTitle": "コネクター設定", "xpack.enterpriseSearch.content.nav.contentTitle": "コンテンツ", - "xpack.enterpriseSearch.content.nav.crawlerSettingsTitle": "Webクローラー設定", "xpack.enterpriseSearch.content.nav.enterpriseSearchOverviewTitle": "概要", "xpack.enterpriseSearch.content.nav.searchIndicesTitle": "インデックスの検索", "xpack.enterpriseSearch.content.nav.workplaceSearchTitle": "Workplace Search", @@ -11758,11 +11755,6 @@ "xpack.enterpriseSearch.overview.gettingStartedSteps.addData.title": "ドキュメントとデータをエンタープライズ サーチに追加", "xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.message": "App SearchやWorkplace Searchでは、検索エンジンを使用して、顧客や社内チーム向けにカスタマイズされた検索エクスペリエンスを構築できます。あるいは、検索UIを使用して、Elasticsearchインデックスに直接接続し、ユーザー向けのクライアント側検索エクスペリエンスを構築できます。", "xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.title": "検索エクスペリエンスを構築", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.description": "導入してすぐにElasticsearchの能力を発揮します。", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.title": "App Searchエンジンの作成", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createASearchEngineButton": "検索エンジンの作成", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.description": "社内チーム向けの安全な検索エクスペリエンス", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.title": "Workplace Searchグループの作成", "xpack.enterpriseSearch.overview.gettingStartedSteps.searchWithElasticsearchLink": "Elasticsearch APIで検索", "xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message": "分析を行い、結果設定を調整して、ユーザーが検索している内容を正確に見つけられるようにします。", "xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title": "検索関連性の調整", @@ -11795,13 +11787,6 @@ "xpack.enterpriseSearch.overview.setupCta.description": "Elastic App Search および Workplace Search を使用して、アプリまたは社内組織に検索を追加できます。検索が簡単になるとどのような利点があるのかについては、動画をご覧ください。", "xpack.enterpriseSearch.overview.setupHeading": "セットアップする製品を選択し、開始してください。", "xpack.enterpriseSearch.overview.subheading": "アプリまたは組織に検索機能を追加できます。", - "xpack.enterpriseSearch.overviewContent.heading": "エンタープライズ サーチへようこそ", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsBody": "このページを表示するためのアクセス権がありません。エラーだと思われる場合は、管理者に問い合わせてください。", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsButtonLabel": "Kibanaダッシュボードに移動", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterBody": "Kibanaダッシュボードに移動", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterLinkLabel": "ドキュメンテーションを表示", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsIllustration": "不十分なアクセス権の例", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsTitle": "パーミッションがありません", "xpack.enterpriseSearch.productSelectorCalloutTitle": "あらゆる規模のチームに対応するエンタープライズ級の機能", "xpack.enterpriseSearch.readOnlyMode.warning": "エンタープライズ サーチは読み取り専用モードです。作成、編集、削除などの変更を実行できません。", "xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "マッピングを追加", @@ -22383,16 +22368,10 @@ "xpack.observability.resources.quick_start": "クイックスタートビデオ", "xpack.observability.resources.title": "リソース", "xpack.observability.resources.training": "無料のObservabilityコース", - "xpack.observability.ruleDetails.actions": "アクション", "xpack.observability.ruleDetails.alerts": "アラート", "xpack.observability.ruleDetails.byWord": "グループ基準", - "xpack.observability.ruleDetails.conditions": "条件{s}", - "xpack.observability.ruleDetails.conditionsTitle": "条件", - "xpack.observability.ruleDetails.connectorsLoadError": "ルールアクションコネクターを読み込めません。理由:{message}", "xpack.observability.ruleDetails.createdWord": "作成済み", - "xpack.observability.ruleDetails.definition": "定義", "xpack.observability.ruleDetails.deleteRule": "ルールの削除", - "xpack.observability.ruleDetails.description": "説明", "xpack.observability.ruleDetails.editRule": "ルールを編集", "xpack.observability.ruleDetails.errorPromptBody": "ルール詳細の読み込みエラーが発生しました。", "xpack.observability.ruleDetails.errorPromptTitle": "ルール詳細を読み込めません", @@ -22401,15 +22380,11 @@ "xpack.observability.ruleDetails.last24h": "(過去24時間)", "xpack.observability.ruleDetails.lastRun": "前回の実行", "xpack.observability.ruleDetails.lastUpdatedMessage": "最終更新", - "xpack.observability.ruleDetails.noActions": "アクションなし", - "xpack.observability.ruleDetails.notifyWhen": "通知", "xpack.observability.ruleDetails.onWord": "日付", "xpack.observability.ruleDetails.rule.alertsTabText": "アラート", "xpack.observability.ruleDetails.rule.eventLogTabText": "実行履歴", "xpack.observability.ruleDetails.ruleIs": "ルールは", "xpack.observability.ruleDetails.ruleLoadError": "ルールを読み込めません。理由:{message}", - "xpack.observability.ruleDetails.ruleType": "ルールタイプ", - "xpack.observability.ruleDetails.runsEvery": "次の間隔で実行", "xpack.observability.ruleDetails.tagsTitle": "タグ", "xpack.observability.ruleDetails.triggreAction.status": "ステータス", "xpack.observability.rules.addRuleButtonLabel": "ルールを作成", @@ -24562,7 +24537,6 @@ "xpack.securitySolution.console.builtInCommands.help.helpTitle": "使用可能なコマンド", "xpack.securitySolution.console.builtInCommands.helpAbout": "使用可能なコマンドのリストを表示", "xpack.securitySolution.console.commandList.footerText": "上記のコマンドの詳細については、{helpOption}引数を使用してください。例:{cmdExample}", - "xpack.securitySolution.console.commandUsage.atLeastOneOptionRequiredMessage": "注記:1つ以上のオプションを使用する必要があります", "xpack.securitySolution.console.commandUsage.inputUsage": "使用方法:", "xpack.securitySolution.console.commandValidation.argSupportedOnlyOnce": "引数{argName}は一度だけ使用できます", "xpack.securitySolution.console.commandValidation.invalidArgValue": "無効な引数値:{argName}。{error}", @@ -26373,7 +26347,6 @@ "xpack.securitySolution.endpointManagemnet.noPermissionsText": "Elastic Security Administrationを使用するために必要なKibana権限がありません。", "xpack.securitySolution.endpointPolicyStatus.revisionNumber": "rev. {revNumber}", "xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "ポリシーが適用されました", - "xpack.securitySolution.endpointResponseActions.isolate.errorMessage": "分離アクションが失敗しました。エラー:{errors}", "xpack.securitySolution.endpointResponseActions.isolate.errorMessageTitle": "失敗", "xpack.securitySolution.endpointResponseActions.isolate.successMessageTitle": "成功", "xpack.securitySolution.endpointResponseActions.status.agentStatus": "エージェントステータス", @@ -28907,15 +28880,10 @@ "xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", "xpack.stackAlerts.esQuery.ui.alertType.defaultActionMessage": "Elasticsearchクエリアラート'\\{\\{alertName\\}\\}'が有効です。\n\n- 値:\\{\\{context.value\\}\\}\n- 満たされた条件:\\{\\{context.conditions\\}\\} over \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\}\n- タイムスタンプ:\\{\\{context.date\\}\\}\n- リンク:\\{\\{context.link\\}\\}", "xpack.stackAlerts.esQuery.ui.alertType.descriptionText": "前回のクエリ実行中に一致が見つかったときにアラートを発行します。", - "xpack.stackAlerts.esQuery.ui.conditionPrompt": "一致数", - "xpack.stackAlerts.esQuery.ui.conditionPrompt.toolTip": "以下で定義された時間枠は最初のルールチェックにのみ適用されます。", "xpack.stackAlerts.esQuery.ui.numQueryMatchesText": "前回の{window}でクエリが{count}個のドキュメントと一致しました。", "xpack.stackAlerts.esQuery.ui.queryEditor": "Elasticsearchクエリエディター", "xpack.stackAlerts.esQuery.ui.queryError": "クエリのテストエラー:{message}", - "xpack.stackAlerts.esQuery.ui.queryPrompt": "Elasticsearchクエリを定義", "xpack.stackAlerts.esQuery.ui.queryPrompt.help": "ElasticsearchクエリDSLドキュメント", - "xpack.stackAlerts.esQuery.ui.queryPrompt.label": "Elasticsearch クエリ", - "xpack.stackAlerts.esQuery.ui.selectIndex": "インデックスとサイズを選択", "xpack.stackAlerts.esQuery.ui.sizeExpression": "サイズ", "xpack.stackAlerts.esQuery.ui.testQuery": "クエリのテスト", "xpack.stackAlerts.esQuery.ui.testQueryIsExecuted": "クエリが実行されます。", @@ -28990,11 +28958,6 @@ "xpack.stackAlerts.indexThreshold.alertTypeTitle": "インデックスしきい値", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "無効な thresholdComparator が指定されました:{comparator}", "xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold]:「{thresholdComparator}」比較子の場合には2つの要素が必要です", - "xpack.stackAlerts.searchSource.ui.conditionPrompt": "一致数", - "xpack.stackAlerts.searchSource.ui.searchQuery": "検索クエリ", - "xpack.stackAlerts.searchSource.ui.selectSizePrompt": "サイズを選択", - "xpack.stackAlerts.searchSource.ui.sizeExpression": "サイズ", - "xpack.stackAlerts.searchThreshold.ui.conditionPrompt": "ドキュメント数が一致するとき", "xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", "xpack.stackAlerts.threshold.ui.alertType.defaultActionMessage": "アラート '\\{\\{alertName\\}\\}' はグループ '\\{\\{context.group\\}\\}' でアクティブです:\n\n- 値:\\{\\{context.value\\}\\}\n- 満たされた条件:\\{\\{context.conditions\\}\\} over \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\}\n- タイムスタンプ:\\{\\{context.date\\}\\}", "xpack.stackAlerts.threshold.ui.alertType.descriptionText": "アグリゲーションされたクエリがしきい値に達したときにアラートを発行します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 45babab2684ee..42836433a8abd 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3890,25 +3890,6 @@ "home.sampleData.logsSpec.webTrafficTitle": "[日志] 网络流量", "home.sampleData.logsSpecDescription": "用于监测 Web 日志的样例数据、可视化和仪表板。", "home.sampleData.logsSpecTitle": "样例 Web 日志", - "homePackages.sampleDataSet.installedLabel": "{name} 已安装", - "homePackages.sampleDataSet.unableToInstallErrorMessage": "无法安装样例数据集:{name}", - "homePackages.sampleDataSet.unableToLoadListErrorMessage": "无法加载样例数据集列表", - "homePackages.sampleDataSet.unableToUninstallErrorMessage": "无法卸载样例数据集:{name}", - "homePackages.sampleDataSet.uninstalledLabel": "{name} 已卸载", - "homePackages.sampleDataCard.addButtonAriaLabel": "添加 {datasetName}", - "homePackages.sampleDataCard.addButtonLabel": "添加数据", - "homePackages.sampleDataCard.addingButtonAriaLabel": "正在添加 {datasetName}", - "homePackages.sampleDataCard.addingButtonLabel": "正在添加", - "homePackages.sampleDataCard.dashboardLinkLabel": "仪表板", - "homePackages.sampleDataCard.default.addButtonAriaLabel": "添加 {datasetName}", - "homePackages.sampleDataCard.default.addButtonLabel": "添加数据", - "homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "无法确认数据集状态,错误:{statusMsg}", - "homePackages.sampleDataCard.removeButtonAriaLabel": "移除 {datasetName}", - "homePackages.sampleDataCard.removeButtonLabel": "移除", - "homePackages.sampleDataCard.removingButtonAriaLabel": "正在移除 {datasetName}", - "homePackages.sampleDataCard.removingButtonLabel": "正在移除", - "homePackages.sampleDataCard.viewDataButtonAriaLabel": "查看 {datasetName}", - "homePackages.sampleDataCard.viewDataButtonLabel": "查看数据", "home.solutionsSection.sectionTitle": "选取您的解决方案", "home.tryButtonLabel": "添加集成", "home.tutorial.addDataToKibanaDescription": "除了添加 {integrationsLink} 以外,您还可以试用样例数据或上传自己的数据。", @@ -4657,6 +4638,25 @@ "home.tutorials.zscalerLogs.nameTitle": "Zscaler 日志", "home.tutorials.zscalerLogs.shortDescription": "使用 Filebeat 从 Zscaler NSS 收集并解析日志。", "home.welcomeTitle": "欢迎使用 Elastic", + "homePackages.sampleDataSet.installedLabel": "{name} 已安装", + "homePackages.sampleDataSet.unableToInstallErrorMessage": "无法安装样例数据集:{name}", + "homePackages.sampleDataSet.unableToLoadListErrorMessage": "无法加载样例数据集列表", + "homePackages.sampleDataSet.unableToUninstallErrorMessage": "无法卸载样例数据集:{name}", + "homePackages.sampleDataSet.uninstalledLabel": "{name} 已卸载", + "homePackages.sampleDataCard.addButtonAriaLabel": "添加 {datasetName}", + "homePackages.sampleDataCard.addButtonLabel": "添加数据", + "homePackages.sampleDataCard.addingButtonAriaLabel": "正在添加 {datasetName}", + "homePackages.sampleDataCard.addingButtonLabel": "正在添加", + "homePackages.sampleDataCard.dashboardLinkLabel": "仪表板", + "homePackages.sampleDataCard.default.addButtonAriaLabel": "添加 {datasetName}", + "homePackages.sampleDataCard.default.addButtonLabel": "添加数据", + "homePackages.sampleDataCard.default.unableToVerifyErrorMessage": "无法确认数据集状态,错误:{statusMsg}", + "homePackages.sampleDataCard.removeButtonAriaLabel": "移除 {datasetName}", + "homePackages.sampleDataCard.removeButtonLabel": "移除", + "homePackages.sampleDataCard.removingButtonAriaLabel": "正在移除 {datasetName}", + "homePackages.sampleDataCard.removingButtonLabel": "正在移除", + "homePackages.sampleDataCard.viewDataButtonAriaLabel": "查看 {datasetName}", + "homePackages.sampleDataCard.viewDataButtonLabel": "查看数据", "indexPatternEditor.aliasLabel": "别名", "indexPatternEditor.createIndex.noMatch": "名称必须匹配一个或多个数据流、索引或索引别名。", "indexPatternEditor.createIndexPattern.stepTime.noTimeFieldOptionLabel": "--- 我不想使用时间筛选 ---", @@ -7828,7 +7828,6 @@ "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText": "输入应附加的 Elastic APM Java 代理的 {versionLink}。", "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.helpText.version": "版本", "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.invalid": "版本无效", - "xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.version.placeHolder": "添加版本", "xpack.apm.fleetIntegration.assets.description": "在 APM 中查看应用程序跟踪和服务地图", "xpack.apm.fleetIntegration.assets.name": "服务", "xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText": "安装 APM 代理", @@ -11651,9 +11650,7 @@ "xpack.enterpriseSearch.appSearch.tokens.update": "API 密钥“{name}”已更新", "xpack.enterpriseSearch.content.description": "Enterprise Search 提供了各种方法以便您轻松搜索数据。从网络爬虫、Elasticsearch 索引、API、直接上传或第三方连接器中选择。", "xpack.enterpriseSearch.content.nav.appSearchTitle": "App Search", - "xpack.enterpriseSearch.content.nav.connectorSettingsTitle": "连接器设置", "xpack.enterpriseSearch.content.nav.contentTitle": "内容", - "xpack.enterpriseSearch.content.nav.crawlerSettingsTitle": "网络爬虫设置", "xpack.enterpriseSearch.content.nav.enterpriseSearchOverviewTitle": "概览", "xpack.enterpriseSearch.content.nav.searchIndicesTitle": "搜索索引", "xpack.enterpriseSearch.content.nav.workplaceSearchTitle": "Workplace Search", @@ -11773,11 +11770,6 @@ "xpack.enterpriseSearch.overview.gettingStartedSteps.addData.title": "将文档和数据添加到 Enterprise Search", "xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.message": "您可以使用搜索引擎为运行 App Search 或 Workplace Search 的客户或内部团队打造定制化的搜索体验。或者,您可以使用搜索 UI 直接连接到 Elasticsearch 索引,为用户构建客户端搜索体验。", "xpack.enterpriseSearch.overview.gettingStartedSteps.buildSearchExperience.title": "构建搜索体验", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.description": "Elasticsearch 功能非常强大,但学起来很快。", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createAppSearchEngine.title": "创建 App Search 引擎", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createASearchEngineButton": "创建搜索引擎", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.description": "面向内部团队的安全搜索体验", - "xpack.enterpriseSearch.overview.gettingStartedSteps.createWorkplaceSearchGroup.title": "创建 Workplace Search 组", "xpack.enterpriseSearch.overview.gettingStartedSteps.searchWithElasticsearchLink": "使用 Elasticsearch API 进行搜索", "xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.message": "深入进行分析并调整结果设置,以帮助用户准确找到他们寻找的内容", "xpack.enterpriseSearch.overview.gettingStartedSteps.tuneSearchExperience.title": "调整搜索相关性", @@ -11810,13 +11802,6 @@ "xpack.enterpriseSearch.overview.setupCta.description": "通过 Elastic App Search 和 Workplace Search,将搜索添加到您的应用或内部组织中。观看视频,了解方便易用的搜索功能可以帮您做些什么。", "xpack.enterpriseSearch.overview.setupHeading": "选择产品进行设置并开始使用。", "xpack.enterpriseSearch.overview.subheading": "将搜索功能添加到您的应用或组织。", - "xpack.enterpriseSearch.overviewContent.heading": "欢迎使用 Enterprise Search", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsBody": "您无权查看此页面。如果您认为这可能是个错误,请联系您的管理员。", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsButtonLabel": "前往 Kibana 仪表板", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterBody": "前往 Kibana 仪表板", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsFooterLinkLabel": "阅读文档", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsIllustration": "权限不足图示", - "xpack.enterpriseSearch.overviewContent.insufficientPermissionsTitle": "权限不足", "xpack.enterpriseSearch.productSelectorCalloutTitle": "适用于大型和小型团队的企业级功能", "xpack.enterpriseSearch.readOnlyMode.warning": "企业搜索处于只读模式。您将无法执行更改,例如创建、编辑或删除。", "xpack.enterpriseSearch.roleMapping.addRoleMappingButtonLabel": "添加映射", @@ -22408,15 +22393,10 @@ "xpack.observability.resources.quick_start": "快速入门视频", "xpack.observability.resources.title": "资源", "xpack.observability.resources.training": "免费的可观测性课程", - "xpack.observability.ruleDetails.actions": "操作", "xpack.observability.ruleDetails.alerts": "告警", "xpack.observability.ruleDetails.byWord": "依据", - "xpack.observability.ruleDetails.conditionsTitle": "条件", - "xpack.observability.ruleDetails.connectorsLoadError": "无法加载规则操作连接器。原因:{message}", "xpack.observability.ruleDetails.createdWord": "创建时间", - "xpack.observability.ruleDetails.definition": "定义", "xpack.observability.ruleDetails.deleteRule": "删除规则", - "xpack.observability.ruleDetails.description": "描述", "xpack.observability.ruleDetails.editRule": "编辑规则", "xpack.observability.ruleDetails.errorPromptBody": "加载规则详情时出现错误。", "xpack.observability.ruleDetails.errorPromptTitle": "无法加载规则详情", @@ -22425,15 +22405,11 @@ "xpack.observability.ruleDetails.last24h": "(过去 24 小时)", "xpack.observability.ruleDetails.lastRun": "上次运行", "xpack.observability.ruleDetails.lastUpdatedMessage": "上次更新时间", - "xpack.observability.ruleDetails.noActions": "无操作", - "xpack.observability.ruleDetails.notifyWhen": "通知", "xpack.observability.ruleDetails.onWord": "在", "xpack.observability.ruleDetails.rule.alertsTabText": "告警", "xpack.observability.ruleDetails.rule.eventLogTabText": "执行历史记录", "xpack.observability.ruleDetails.ruleIs": "规则为", "xpack.observability.ruleDetails.ruleLoadError": "无法加载规则。原因:{message}", - "xpack.observability.ruleDetails.ruleType": "规则类型", - "xpack.observability.ruleDetails.runsEvery": "运行间隔", "xpack.observability.ruleDetails.tagsTitle": "标签", "xpack.observability.ruleDetails.triggreAction.status": "状态", "xpack.observability.rules.addRuleButtonLabel": "创建规则", @@ -24586,7 +24562,6 @@ "xpack.securitySolution.console.builtInCommands.help.helpTitle": "可用命令", "xpack.securitySolution.console.builtInCommands.helpAbout": "查看可用命令列表", "xpack.securitySolution.console.commandList.footerText": "有关上述命令的更多详情,请使用 {helpOption} 参数。示例:{cmdExample}", - "xpack.securitySolution.console.commandUsage.atLeastOneOptionRequiredMessage": "注意:必须至少使用一个选项", "xpack.securitySolution.console.commandUsage.inputUsage": "用法:", "xpack.securitySolution.console.commandValidation.argSupportedOnlyOnce": "参数只能使用一次:{argName}", "xpack.securitySolution.console.commandValidation.invalidArgValue": "无效的参数值:{argName}。{error}", @@ -26398,7 +26373,6 @@ "xpack.securitySolution.endpointManagemnet.noPermissionsText": "您没有所需的 Kibana 权限,无法使用 Elastic Security 管理", "xpack.securitySolution.endpointPolicyStatus.revisionNumber": "修订版 {revNumber}", "xpack.securitySolution.endpointPolicyStatus.tooltipTitleLabel": "已应用策略", - "xpack.securitySolution.endpointResponseActions.isolate.errorMessage": "隔离操作失败,出现:{errors}", "xpack.securitySolution.endpointResponseActions.isolate.errorMessageTitle": "失败", "xpack.securitySolution.endpointResponseActions.isolate.successMessageTitle": "成功", "xpack.securitySolution.endpointResponseActions.status.agentStatus": "代理状态", @@ -28933,15 +28907,10 @@ "xpack.stackAlerts.esQuery.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", "xpack.stackAlerts.esQuery.ui.alertType.defaultActionMessage": "Elasticsearch 查询告警“\\{\\{alertName\\}\\}”处于活动状态:\n\n- 值:\\{\\{context.value\\}\\}\n- 满足的条件:\\{\\{context.conditions\\}\\} 超过 \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\}\n- 时间戳:\\{\\{context.date\\}\\}\n- 链接:\\{\\{context.link\\}\\}", "xpack.stackAlerts.esQuery.ui.alertType.descriptionText": "在运行最新查询期间找到匹配项时告警。", - "xpack.stackAlerts.esQuery.ui.conditionPrompt": "当匹配数目", - "xpack.stackAlerts.esQuery.ui.conditionPrompt.toolTip": "下面定义的时间窗口仅适用于第一次规则检查。", "xpack.stackAlerts.esQuery.ui.numQueryMatchesText": "查询在过去 {window} 匹配 {count} 个文档。", "xpack.stackAlerts.esQuery.ui.queryEditor": "Elasticsearch 查询编辑器", "xpack.stackAlerts.esQuery.ui.queryError": "测试查询时出错:{message}", - "xpack.stackAlerts.esQuery.ui.queryPrompt": "定义 Elasticsearch 查询", "xpack.stackAlerts.esQuery.ui.queryPrompt.help": "Elasticsearch 查询 DSL 文档", - "xpack.stackAlerts.esQuery.ui.queryPrompt.label": "Elasticsearch 查询", - "xpack.stackAlerts.esQuery.ui.selectIndex": "选择索引和大小", "xpack.stackAlerts.esQuery.ui.sizeExpression": "大小", "xpack.stackAlerts.esQuery.ui.testQuery": "测试查询", "xpack.stackAlerts.esQuery.ui.testQueryIsExecuted": "已执行查询。", @@ -29016,11 +28985,6 @@ "xpack.stackAlerts.indexThreshold.alertTypeTitle": "索引阈值", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "指定的 thresholdComparator 无效:{comparator}", "xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold]:对于“{thresholdComparator}”比较运算符,必须包含两个元素", - "xpack.stackAlerts.searchSource.ui.conditionPrompt": "当匹配数目", - "xpack.stackAlerts.searchSource.ui.searchQuery": "搜索查询", - "xpack.stackAlerts.searchSource.ui.selectSizePrompt": "选择大小", - "xpack.stackAlerts.searchSource.ui.sizeExpression": "大小", - "xpack.stackAlerts.searchThreshold.ui.conditionPrompt": "文档数量匹配时", "xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", "xpack.stackAlerts.threshold.ui.alertType.defaultActionMessage": "组“\\{\\{context.group\\}\\}”的告警“\\{\\{alertName\\}\\}”处于活动状态:\n\n- 值:\\{\\{context.value\\}\\}\n- 满足的条件:\\{\\{context.conditions\\}\\} 超过 \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\}\n- 时间戳:\\{\\{context.date\\}\\}", "xpack.stackAlerts.threshold.ui.alertType.descriptionText": "聚合查询达到阈值时告警。", diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index a872ad6fab5c0..1458d37834800 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -8,7 +8,7 @@ "server": true, "ui": true, "optionalPlugins": ["cloud", "features", "home", "spaces"], - "requiredPlugins": ["management", "charts", "data", "kibanaReact", "kibanaUtils", "savedObjects", "unifiedSearch", "dataViews", "alerting", "actions"], + "requiredPlugins": ["management", "charts", "data", "kibanaReact", "kibanaUtils", "savedObjects", "unifiedSearch", "dataViews", "dataViewEditor", "alerting", "actions"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], "requiredBundles": ["alerting", "esUiShared", "kibanaReact", "kibanaUtils", "actions"] diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index d47529c47c19d..e63999edf31a9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -17,6 +17,7 @@ import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; @@ -45,6 +46,7 @@ export interface TriggersAndActionsUiServices extends CoreStart { actions: ActionsPublicPluginSetup; data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; + dataViewEditor: DataViewEditorStart; charts: ChartsPluginStart; alerting?: AlertingStart; spaces?: SpacesPluginStart; diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts similarity index 66% rename from x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts rename to x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts index cae7607f1d084..4e89e43d20e13 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rule_action_connectors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_fetch_rule_action_connectors.ts @@ -6,21 +6,31 @@ */ import { useEffect, useState, useCallback } from 'react'; -import { ActionConnector, loadAllActions } from '@kbn/triggers-actions-ui-plugin/public'; import { intersectionBy } from 'lodash'; -import { FetchRuleActionConnectorsProps } from '../pages/rule_details/types'; -import { ACTIONS_LOAD_ERROR } from '../pages/rule_details/translations'; +import { i18n } from '@kbn/i18n'; +import { ActionConnector, loadAllActions } from '../..'; +import { useKibana } from '../../common/lib/kibana'; +const ACTIONS_LOAD_ERROR = (errorMessage: string) => + i18n.translate('xpack.triggersActionsUI.ruleDetails.connectorsLoadError', { + defaultMessage: 'Unable to load rule actions connectors. Reason: {message}', + values: { message: errorMessage }, + }); interface FetchActionConnectors { isLoadingActionConnectors: boolean; actionConnectors: Array<ActionConnector<Record<string, unknown>>>; errorActionConnectors?: string; } +interface FetchRuleActionConnectorsProps { + ruleActions: any[]; +} + +export function useFetchRuleActionConnectors({ ruleActions }: FetchRuleActionConnectorsProps) { + const { + http, + notifications: { toasts }, + } = useKibana().services; -export function useFetchRuleActionConnectors({ - http, - ruleActions, -}: FetchRuleActionConnectorsProps) { const [actionConnectors, setActionConnector] = useState<FetchActionConnectors>({ isLoadingActionConnectors: true, actionConnectors: [] as Array<ActionConnector<Record<string, unknown>>>, @@ -47,15 +57,17 @@ export function useFetchRuleActionConnectors({ actionConnectors: actions, })); } catch (error) { + const errorMsg = ACTIONS_LOAD_ERROR( + error instanceof Error ? error.message : typeof error === 'string' ? error : '' + ); setActionConnector((oldState: FetchActionConnectors) => ({ ...oldState, isLoadingActionConnectors: false, - errorActionConnectors: ACTIONS_LOAD_ERROR( - error instanceof Error ? error.message : typeof error === 'string' ? error : '' - ), + errorActionConnectors: errorMsg, })); + toasts.addDanger({ title: errorMsg }); } - }, [http, ruleActions]); + }, [http, ruleActions, toasts]); useEffect(() => { fetchRuleActionConnectors(); }, [fetchRuleActionConnectors]); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx index 1fda39cd84ee9..3a7d693ce6375 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.test.tsx @@ -12,6 +12,7 @@ import { EcsFieldsResponse } from '@kbn/rule-registry-plugin/common/search_strat import { AlertsTable } from './alerts_table'; import { AlertsField } from '../../../types'; +import { EuiButtonIcon, EuiFlexItem } from '@elastic/eui'; jest.mock('@kbn/data-plugin/public'); @@ -129,5 +130,103 @@ describe('AlertsTable', () => { expect(wrapper.queryByTestId('testCell')).not.toBe(null); }); }); + + describe('actions column', () => { + it('should load actions set in config', () => { + const customTableProps = { + ...tableProps, + alertsTableConfiguration: { + ...alertsTableConfiguration, + useActionsColumn: () => { + return { + renderCustomActionsRow: () => { + return ( + <> + <EuiFlexItem grow={false}> + <EuiButtonIcon + iconType="analyzeEvent" + color="primary" + onClick={() => {}} + size="s" + data-test-subj="testActionColumn" + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonIcon + iconType="analyzeEvent" + color="primary" + onClick={() => {}} + size="s" + data-test-subj="testActionColumn2" + /> + </EuiFlexItem> + </> + ); + }, + }; + }, + }, + }; + + const { queryByTestId } = render(<AlertsTable {...customTableProps} />); + expect(queryByTestId('testActionColumn')).not.toBe(null); + expect(queryByTestId('testActionColumn2')).not.toBe(null); + expect(queryByTestId('expandColumnCellOpenFlyoutButton-0')).not.toBe(null); + }); + + it('should not add expansion action when not set', () => { + const customTableProps = { + ...tableProps, + showExpandToDetails: false, + alertsTableConfiguration: { + ...alertsTableConfiguration, + useActionsColumn: () => { + return { + renderCustomActionsRow: () => { + return ( + <> + <EuiFlexItem grow={false}> + <EuiButtonIcon + iconType="analyzeEvent" + color="primary" + onClick={() => {}} + size="s" + data-test-subj="testActionColumn" + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButtonIcon + iconType="analyzeEvent" + color="primary" + onClick={() => {}} + size="s" + data-test-subj="testActionColumn2" + /> + </EuiFlexItem> + </> + ); + }, + }; + }, + }, + }; + + const { queryByTestId } = render(<AlertsTable {...customTableProps} />); + expect(queryByTestId('testActionColumn')).not.toBe(null); + expect(queryByTestId('testActionColumn2')).not.toBe(null); + expect(queryByTestId('expandColumnCellOpenFlyoutButton-0')).toBe(null); + }); + + it('should render no action column if there is neither the action nor the expand action config is set', () => { + const customTableProps = { + ...tableProps, + showExpandToDetails: false, + }; + + const { queryByTestId } = render(<AlertsTable {...customTableProps} />); + expect(queryByTestId('expandColumnHeaderLabel')).toBe(null); + expect(queryByTestId('expandColumnCellOpenFlyoutButton')).toBe(null); + }); + }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx index a46105454bea0..37108a2da260d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/alerts_table.tsx @@ -22,9 +22,11 @@ import { ALERTS_TABLE_CONTROL_COLUMNS_ACTIONS_LABEL, ALERTS_TABLE_CONTROL_COLUMNS_VIEW_DETAILS_LABEL, } from './translations'; + import './alerts_table.scss'; export const ACTIVE_ROW_CLASS = 'alertsTableActiveRow'; +const DEFAULT_ACTIONS_COLUMNS_WIDTH = 75; const AlertsFlyout = lazy(() => import('./alerts_flyout')); const GridStyles: EuiDataGridStyle = { border: 'none', @@ -57,6 +59,8 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab pageIndex: activePage, pageSize: props.pageSize, }); + const { useActionsColumn = () => ({ renderCustomActionsRow: undefined, width: undefined }) } = + props.alertsTableConfiguration; const [visibleColumns, setVisibleColumns] = useState(props.visibleColumns); @@ -71,49 +75,61 @@ const AlertsTable: React.FunctionComponent<AlertsTableProps> = (props: AlertsTab [onColumnsChange, props.columns] ); + const { renderCustomActionsRow, width: actionsColumnWidth = DEFAULT_ACTIONS_COLUMNS_WIDTH } = + useActionsColumn(); + const leadingControlColumns = useMemo(() => { + if (!props.showExpandToDetails && !renderCustomActionsRow) return props.leadingControlColumns; + return [ - ...(props.showExpandToDetails - ? [ - { - id: 'expandColumn', - width: 75, - headerCellRender: () => { - return ( - <span data-test-subj="expandColumnHeaderLabel"> - {ALERTS_TABLE_CONTROL_COLUMNS_ACTIONS_LABEL} - </span> - ); - }, - rowCellRender: (cveProps: EuiDataGridCellValueElementProps) => { - const { visibleRowIndex } = cveProps as EuiDataGridCellValueElementProps & { - visibleRowIndex: number; - }; - return ( - <EuiFlexGroup gutterSize="none" responsive={false}> - <EuiFlexItem grow={false}> - <EuiToolTip content={ALERTS_TABLE_CONTROL_COLUMNS_VIEW_DETAILS_LABEL}> - <EuiButtonIcon - size="s" - iconType="expand" - color="text" - onClick={() => { - setFlyoutAlertIndex(visibleRowIndex); - }} - data-test-subj={`expandColumnCellOpenFlyoutButton-${visibleRowIndex}`} - aria-label={ALERTS_TABLE_CONTROL_COLUMNS_VIEW_DETAILS_LABEL} - /> - </EuiToolTip> - </EuiFlexItem> - </EuiFlexGroup> - ); - }, - }, - ] - : []), + { + id: 'expandColumn', + width: actionsColumnWidth, + headerCellRender: () => { + return ( + <span data-test-subj="expandColumnHeaderLabel"> + {ALERTS_TABLE_CONTROL_COLUMNS_ACTIONS_LABEL} + </span> + ); + }, + rowCellRender: (cveProps: EuiDataGridCellValueElementProps) => { + const { visibleRowIndex } = cveProps as EuiDataGridCellValueElementProps & { + visibleRowIndex: number; + }; + + return ( + <EuiFlexGroup gutterSize="none" responsive={false}> + {props.showExpandToDetails && ( + <EuiFlexItem grow={false}> + <EuiToolTip content={ALERTS_TABLE_CONTROL_COLUMNS_VIEW_DETAILS_LABEL}> + <EuiButtonIcon + size="s" + iconType="expand" + color="primary" + onClick={() => { + setFlyoutAlertIndex(visibleRowIndex); + }} + data-test-subj={`expandColumnCellOpenFlyoutButton-${visibleRowIndex}`} + aria-label={ALERTS_TABLE_CONTROL_COLUMNS_VIEW_DETAILS_LABEL} + /> + </EuiToolTip> + </EuiFlexItem> + )} + {renderCustomActionsRow && renderCustomActionsRow(alerts[visibleRowIndex])} + </EuiFlexGroup> + ); + }, + }, ...props.leadingControlColumns, ]; - }, [props.leadingControlColumns, props.showExpandToDetails, setFlyoutAlertIndex]); + }, [ + actionsColumnWidth, + alerts, + props.leadingControlColumns, + props.showExpandToDetails, + renderCustomActionsRow, + setFlyoutAlertIndex, + ]); useEffect(() => { // Row classes do not deal with visible row indices so we need to handle page offset diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx index 749ddb2ced7e5..063f766c33ad0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx @@ -49,6 +49,9 @@ export const RulesList = suspendedComponentWithProps( export const RulesListNotifyBadge = suspendedComponentWithProps( lazy(() => import('./rules_list/components/rules_list_notify_badge')) ); +export const RuleDefinition = suspendedComponentWithProps( + lazy(() => import('./rule_details/components/rule_definition')) +); export const RuleTagBadge = suspendedComponentWithProps( lazy(() => import('./rules_list/components/rule_tag_badge')) ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions.test.tsx new file mode 100644 index 0000000000000..2f0fea9657a61 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 { mount } from 'enzyme'; +import { nextTick } from '@kbn/test-jest-helpers'; +import { act } from 'react-dom/test-utils'; +import { RuleActions } from './rule_actions'; +import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; +import { ActionConnector, ActionTypeModel, RuleAction } from '../../../../types'; +import * as useFetchRuleActionConnectorsHook from '../../../hooks/use_fetch_rule_action_connectors'; + +const actionTypeRegistry = actionTypeRegistryMock.create(); +const mockedUseFetchRuleActionConnectorsHook = jest.spyOn( + useFetchRuleActionConnectorsHook, + 'useFetchRuleActionConnectors' +); +describe('Rule Actions', () => { + async function setup() { + const ruleActions = [ + { + id: '1', + group: 'metrics.inventory_threshold.fired', + actionTypeId: '.server-log', + params: {}, + }, + { + id: '2', + group: 'metrics.inventory_threshold.fired', + actionTypeId: '.slack', + params: {}, + }, + ] as RuleAction[]; + + mockedUseFetchRuleActionConnectorsHook.mockReturnValue({ + isLoadingActionConnectors: false, + actionConnectors: [ + { + id: 'f57cabc0-e660-11ec-8241-7deb55b17f15', + name: 'logs', + config: {}, + actionTypeId: '.server-log', + }, + { + id: '05b7ab30-e683-11ec-843b-213c67313f8c', + name: 'Slack', + actionTypeId: '.slack', + }, + ] as Array<ActionConnector<Record<string, unknown>>>, + errorActionConnectors: undefined, + reloadRuleActionConnectors: jest.fn(), + }); + + actionTypeRegistry.list.mockReturnValue([ + { id: '.server-log', iconClass: 'logsApp' }, + { id: '.slack', iconClass: 'logoSlack' }, + { id: '.email', iconClass: 'email' }, + { id: '.index', iconClass: 'indexOpen' }, + ] as ActionTypeModel[]); + + const wrapper = mount( + <RuleActions ruleActions={ruleActions} actionTypeRegistry={actionTypeRegistry} /> + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + return wrapper; + } + + it("renders rule action connector icons for user's selected rule actions", async () => { + const wrapper = await setup(); + expect(mockedUseFetchRuleActionConnectorsHook).toHaveBeenCalledTimes(1); + expect(wrapper.find('[data-euiicon-type]').length).toBe(2); + expect(wrapper.find('[data-euiicon-type="logsApp"]').length).toBe(1); + expect(wrapper.find('[data-euiicon-type="logoSlack"]').length).toBe(1); + expect(wrapper.find('[data-euiicon-type="index"]').length).toBe(0); + expect(wrapper.find('[data-euiicon-type="email"]').length).toBe(0); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions.tsx similarity index 70% rename from x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions.tsx index 7feb8c8d27186..ddae5bf09317b 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/components/actions.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiText, EuiSpacer, @@ -14,33 +14,24 @@ import { IconType, EuiLoadingSpinner, } from '@elastic/eui'; -import { suspendedComponentWithProps } from '@kbn/triggers-actions-ui-plugin/public'; import { i18n } from '@kbn/i18n'; -import { ActionsProps } from '../types'; +import { ActionTypeRegistryContract, RuleAction, suspendedComponentWithProps } from '../../../..'; import { useFetchRuleActionConnectors } from '../../../hooks/use_fetch_rule_action_connectors'; -import { useKibana } from '../../../utils/kibana_react'; -export function Actions({ ruleActions, actionTypeRegistry }: ActionsProps) { - const { - http, - notifications: { toasts }, - } = useKibana().services; - const { isLoadingActionConnectors, actionConnectors, errorActionConnectors } = - useFetchRuleActionConnectors({ - http, - ruleActions, - }); - useEffect(() => { - if (errorActionConnectors) { - toasts.addDanger({ title: errorActionConnectors }); - } - }, [errorActionConnectors, toasts]); +export interface RuleActionsProps { + ruleActions: RuleAction[]; + actionTypeRegistry: ActionTypeRegistryContract; +} +export function RuleActions({ ruleActions, actionTypeRegistry }: RuleActionsProps) { + const { isLoadingActionConnectors, actionConnectors } = useFetchRuleActionConnectors({ + ruleActions, + }); if (!actionConnectors || actionConnectors.length <= 0) return ( <EuiFlexItem> <EuiText size="s"> - {i18n.translate('xpack.observability.ruleDetails.noActions', { + {i18n.translate('xpack.triggersActionsUI.ruleDetails.noActions', { defaultMessage: 'No actions', })} </EuiText> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx new file mode 100644 index 0000000000000..4b065bcd63aa9 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.test.tsx @@ -0,0 +1,222 @@ +/* + * 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 { mount, ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { ALERTS_FEATURE_ID } from '@kbn/alerting-plugin/common'; +import { nextTick } from '@kbn/test-jest-helpers'; +import { RuleDefinition } from './rule_definition'; +import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; +import { ActionTypeModel, Rule, RuleTypeModel } from '../../../../types'; +import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; + +jest.mock('./rule_actions', () => ({ + RuleActions: () => { + return <></>; + }, +})); + +jest.mock('../../../lib/capabilities', () => ({ + hasAllPrivilege: jest.fn(() => true), + hasSaveRulesCapability: jest.fn(() => true), + hasExecuteActionsCapability: jest.fn(() => true), + hasManageApiKeysCapability: jest.fn(() => true), +})); +jest.mock('../../../../common/lib/kibana'); +jest.mock('../../../..', () => ({ + useLoadRuleTypes: jest.fn(), +})); +const { useLoadRuleTypes } = jest.requireMock('../../../..'); +const ruleTypes = [ + { + id: 'test_rule_type', + name: 'some rule type', + actionGroups: [{ id: 'default', name: 'Default' }], + recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + actionVariables: { context: [], state: [] }, + defaultActionGroupId: 'default', + producer: ALERTS_FEATURE_ID, + minimumLicenseRequired: 'basic', + enabledInLicense: true, + authorizedConsumers: { + [ALERTS_FEATURE_ID]: { read: true, all: false }, + }, + ruleTaskTimeout: '1m', + }, +]; + +const mockedRuleTypeIndex = new Map( + Object.entries({ + test_rule_type: { + enabledInLicense: true, + id: 'test_rule_type', + name: 'test rule', + }, + '2': { + enabledInLicense: true, + id: '2', + name: 'test rule ok', + }, + '3': { + enabledInLicense: true, + id: '3', + name: 'test rule pending', + }, + }) +); + +describe('Rule Definition', () => { + let wrapper: ReactWrapper; + async function setup() { + const actionTypeRegistry = actionTypeRegistryMock.create(); + const ruleTypeRegistry = ruleTypeRegistryMock.create(); + const mockedRule = mockRule(); + jest.mock('../../../lib/capabilities', () => ({ + hasAllPrivilege: jest.fn(() => true), + hasSaveRulesCapability: jest.fn(() => true), + hasExecuteActionsCapability: jest.fn(() => true), + hasManageApiKeysCapability: jest.fn(() => true), + })); + ruleTypeRegistry.has.mockReturnValue(true); + const ruleTypeR: RuleTypeModel = { + id: 'my-rule-type', + iconClass: 'test', + description: 'Rule when testing', + documentationUrl: 'https://localhost.local/docs', + validate: () => { + return { errors: {} }; + }, + ruleParamsExpression: jest.fn(), + requiresAppContext: false, + }; + ruleTypeRegistry.get.mockReturnValue(ruleTypeR); + actionTypeRegistry.list.mockReturnValue([ + { id: '.server-log', iconClass: 'logsApp' }, + { id: '.slack', iconClass: 'logoSlack' }, + { id: '.email', iconClass: 'email' }, + { id: '.index', iconClass: 'indexOpen' }, + ] as ActionTypeModel[]); + + useLoadRuleTypes.mockReturnValue({ ruleTypes, ruleTypeIndex: mockedRuleTypeIndex }); + + wrapper = mount( + <RuleDefinition + rule={mockedRule} + actionTypeRegistry={actionTypeRegistry} + onEditRule={jest.fn()} + ruleTypeRegistry={ruleTypeRegistry} + /> + ); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + } + + beforeAll(async () => await setup()); + + it('renders rule definition ', async () => { + expect(wrapper.find('[data-test-subj="ruleSummaryRuleDefinition"]')).toBeTruthy(); + }); + + it('show rule type name from "useLoadRuleTypes"', async () => { + expect(useLoadRuleTypes).toHaveBeenCalledTimes(2); + const ruleType = wrapper.find('[data-test-subj="ruleSummaryRuleType"]'); + expect(ruleType).toBeTruthy(); + expect(ruleType.find('div.euiText').text()).toEqual( + mockedRuleTypeIndex.get(mockRule().ruleTypeId)?.name + ); + }); + + it('show rule type description "', async () => { + const ruleDescription = wrapper.find('[data-test-subj="ruleSummaryRuleDescription"]'); + expect(ruleDescription).toBeTruthy(); + expect(ruleDescription.find('div.euiText').text()).toEqual('Rule when testing'); + }); + + it('show rule conditions "', async () => { + const ruleConditions = wrapper.find('[data-test-subj="ruleSummaryRuleConditions"]'); + expect(ruleConditions).toBeTruthy(); + expect(ruleConditions.find('div.euiText').text()).toEqual(`0 conditions`); + }); + + it('show rule interval with human readable value', async () => { + const ruleInterval = wrapper.find('[data-test-subj="ruleSummaryRuleInterval"]'); + expect(ruleInterval).toBeTruthy(); + expect(ruleInterval.find('div.euiText').text()).toEqual('1 sec'); + }); + + it('show edit button when user has permissions', async () => { + const editButton = wrapper.find('[data-test-subj="ruleDetailsEditButton"]'); + expect(editButton).toBeTruthy(); + }); + + it('hide edit button when user DOES NOT have permissions', async () => { + jest.mock('../../../lib/capabilities', () => ({ + hasAllPrivilege: jest.fn(() => false), + hasSaveRulesCapability: jest.fn(() => true), + hasExecuteActionsCapability: jest.fn(() => true), + hasManageApiKeysCapability: jest.fn(() => true), + })); + const editButton = wrapper.find('[data-test-subj="ruleDetailsEditButton"]'); + expect(editButton).toMatchObject({}); + }); +}); +function mockRule(): Rule { + return { + id: '1', + name: 'test rule', + tags: ['tag1'], + enabled: true, + ruleTypeId: 'test_rule_type', + schedule: { interval: '1s' }, + actions: [], + params: { name: 'test rule type name' }, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + createdAt: new Date(), + updatedAt: new Date(), + consumer: 'alerts', + notifyWhen: 'onActiveAlert', + executionStatus: { + status: 'active', + lastDuration: 500, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + }, + monitoring: { + execution: { + history: [ + { + success: true, + duration: 1000000, + timestamp: 1234567, + }, + { + success: true, + duration: 200000, + timestamp: 1234567, + }, + { + success: false, + duration: 300000, + timestamp: 1234567, + }, + ], + calculated_metrics: { + success_ratio: 0.66, + p50: 200000, + p95: 300000, + p99: 300000, + }, + }, + }, + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx new file mode 100644 index 0000000000000..7b74e414d200a --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_definition.tsx @@ -0,0 +1,238 @@ +/* + * 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, { useState, useEffect, useMemo } from 'react'; +import { + EuiText, + EuiSpacer, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { formatDuration } from '@kbn/alerting-plugin/common'; +import { RuleDefinitionProps } from '../../../../types'; +import { RuleType, useLoadRuleTypes } from '../../../..'; +import { useKibana } from '../../../../common/lib/kibana'; +import { hasAllPrivilege, hasExecuteActionsCapability } from '../../../lib/capabilities'; +import { NOTIFY_WHEN_OPTIONS } from '../../rule_form/rule_notify_when'; +import { RuleActions } from './rule_actions'; +import { RuleEdit } from '../../rule_form'; + +const OBSERVABILITY_SOLUTIONS = ['logs', 'uptime', 'infrastructure', 'apm']; + +export const RuleDefinition: React.FunctionComponent<RuleDefinitionProps> = ({ + rule, + actionTypeRegistry, + ruleTypeRegistry, + onEditRule, +}) => { + const { + application: { capabilities }, + } = useKibana().services; + + const [editFlyoutVisible, setEditFlyoutVisible] = useState<boolean>(false); + const [ruleType, setRuleType] = useState<RuleType>(); + const { ruleTypes, ruleTypeIndex } = useLoadRuleTypes({ + filteredSolutions: OBSERVABILITY_SOLUTIONS, + }); + + const getRuleType = useMemo(() => { + if (ruleTypes.length && rule) { + return ruleTypes.find((type) => type.id === rule.ruleTypeId); + } + }, [rule, ruleTypes]); + + useEffect(() => { + setRuleType(getRuleType); + }, [getRuleType]); + + const getRuleConditionsWording = () => { + const numberOfConditions = rule?.params.criteria ? (rule?.params.criteria as any[]).length : 0; + return i18n.translate('xpack.triggersActionsUI.ruleDetails.conditions', { + defaultMessage: '{numberOfConditions, plural, one {# condition} other {# conditions}}', + values: { numberOfConditions }, + }); + }; + const getNotifyText = () => + NOTIFY_WHEN_OPTIONS.find((options) => options.value === rule?.notifyWhen)?.inputDisplay || + rule?.notifyWhen; + + const canExecuteActions = hasExecuteActionsCapability(capabilities); + const canSaveRule = + rule && + hasAllPrivilege(rule, ruleType) && + // if the rule has actions, can the user save the rule's action params + (canExecuteActions || (!canExecuteActions && rule.actions.length === 0)); + const hasEditButton = + // can the user save the rule + canSaveRule && + // is this rule type editable from within Rules Management + (ruleTypeRegistry.has(rule.ruleTypeId) + ? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext + : false); + return ( + <EuiFlexItem data-test-subj="ruleSummaryRuleDefinition" grow={3}> + <EuiPanel color="subdued" hasBorder={false} paddingSize={'m'}> + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiTitle size="s"> + <EuiFlexItem grow={false}> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.definition', { + defaultMessage: 'Definition', + })} + </EuiFlexItem> + </EuiTitle> + {hasEditButton && ( + <EuiFlexItem grow={false}> + <EuiButtonEmpty + data-test-subj="ruleDetailsEditButton" + iconType={'pencil'} + onClick={() => setEditFlyoutVisible(true)} + /> + </EuiFlexItem> + )} + </EuiFlexGroup> + + <EuiSpacer size="m" /> + + <EuiFlexGroup alignItems="baseline"> + <EuiFlexItem> + <EuiFlexGroup> + <ItemTitleRuleSummary> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.ruleType', { + defaultMessage: 'Rule type', + })} + </ItemTitleRuleSummary> + <ItemValueRuleSummary + data-test-subj="ruleSummaryRuleType" + itemValue={ruleTypeIndex.get(rule.ruleTypeId)?.name || rule.ruleTypeId} + /> + </EuiFlexGroup> + + <EuiSpacer size="m" /> + + <EuiFlexGroup alignItems="flexStart" responsive={false}> + <ItemTitleRuleSummary> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.description', { + defaultMessage: 'Description', + })} + </ItemTitleRuleSummary> + <ItemValueRuleSummary + data-test-subj="ruleSummaryRuleDescription" + itemValue={ruleTypeRegistry.get(rule.ruleTypeId).description} + /> + </EuiFlexGroup> + + <EuiSpacer size="m" /> + + <EuiFlexGroup> + <ItemTitleRuleSummary> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.conditionsTitle', { + defaultMessage: 'Conditions', + })} + </ItemTitleRuleSummary> + <EuiFlexItem grow={3}> + <EuiFlexGroup data-test-subj="ruleSummaryRuleConditions" alignItems="center"> + {hasEditButton ? ( + <EuiButtonEmpty onClick={() => setEditFlyoutVisible(true)}> + <EuiText size="s">{getRuleConditionsWording()}</EuiText> + </EuiButtonEmpty> + ) : ( + <EuiText size="s">{getRuleConditionsWording()}</EuiText> + )} + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup> + <ItemTitleRuleSummary> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.runsEvery', { + defaultMessage: 'Runs every', + })} + </ItemTitleRuleSummary> + + <ItemValueRuleSummary + data-test-subj="ruleSummaryRuleInterval" + itemValue={formatDuration(rule.schedule.interval)} + /> + </EuiFlexGroup> + + <EuiSpacer size="m" /> + + <EuiFlexGroup> + <ItemTitleRuleSummary> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.notifyWhen', { + defaultMessage: 'Notify', + })} + </ItemTitleRuleSummary> + <ItemValueRuleSummary itemValue={String(getNotifyText())} /> + </EuiFlexGroup> + + <EuiSpacer size="m" /> + <EuiFlexGroup alignItems="baseline"> + <ItemTitleRuleSummary> + {i18n.translate('xpack.triggersActionsUI.ruleDetails.actions', { + defaultMessage: 'Actions', + })} + </ItemTitleRuleSummary> + <EuiFlexItem grow={3}> + <RuleActions ruleActions={rule.actions} actionTypeRegistry={actionTypeRegistry} /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> + </EuiPanel> + {editFlyoutVisible && ( + <RuleEdit + onSave={() => { + setEditFlyoutVisible(false); + return onEditRule(); + }} + initialRule={rule} + onClose={() => setEditFlyoutVisible(false)} + ruleTypeRegistry={ruleTypeRegistry} + actionTypeRegistry={actionTypeRegistry} + /> + )} + </EuiFlexItem> + ); +}; + +export interface ItemTitleRuleSummaryProps { + children: string; +} +export interface ItemValueRuleSummaryProps { + itemValue: string; + extraSpace?: boolean; +} + +function ItemValueRuleSummary({ + itemValue, + extraSpace = true, + ...otherProps +}: ItemValueRuleSummaryProps) { + return ( + <EuiFlexItem grow={extraSpace ? 3 : 1} {...otherProps}> + <EuiText size="s">{itemValue}</EuiText> + </EuiFlexItem> + ); +} + +function ItemTitleRuleSummary({ children }: ItemTitleRuleSummaryProps) { + return ( + <EuiTitle size="xxs"> + <EuiFlexItem style={{ whiteSpace: 'nowrap' }} grow={1}> + {children} + </EuiFlexItem> + </EuiTitle> + ); +} + +// eslint-disable-next-line import/no-default-export +export { RuleDefinition as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/confirm_rule_close.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/confirm_rule_close.tsx index 1b3ad3473b7ec..f553755c430e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/confirm_rule_close.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/confirm_rule_close.tsx @@ -26,6 +26,7 @@ export const ConfirmRuleClose: React.FC<Props> = ({ onConfirm, onCancel }) => { )} onCancel={onCancel} onConfirm={onConfirm} + buttonColor="danger" confirmButtonText={i18n.translate( 'xpack.triggersActionsUI.sections.confirmRuleClose.confirmRuleCloseConfirmButtonText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index 64137c8d40880..bcf81d6c4f150 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -207,9 +207,12 @@ export const RuleEdit = ({ canChangeTrigger={false} setHasActionsDisabled={setHasActionsDisabled} setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector} - operation="i18n.translate('xpack.triggersActionsUI.sections.ruleEdit.operationName', { - defaultMessage: 'edit', - })" + operation={i18n.translate( + 'xpack.triggersActionsUI.sections.ruleEdit.operationName', + { + defaultMessage: 'edit', + } + )} metadata={metadata} /> </EuiFlyoutBody> diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx index d99de651a57e6..9f287f5f066f2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/for_the_last.tsx @@ -67,7 +67,7 @@ export const ForLastExpression = ({ } )} data-test-subj="forLastExpression" - value={`${timeWindowSize} ${getTimeUnitLabel( + value={`${timeWindowSize ?? '?'} ${getTimeUnitLabel( timeWindowUnit as TIME_UNITS, (timeWindowSize ?? '').toString() )}`} @@ -97,13 +97,10 @@ export const ForLastExpression = ({ </ClosablePopoverTitle> <EuiFlexGroup> <EuiFlexItem grow={false}> - <EuiFormRow - isInvalid={errors.timeWindowSize.length > 0 && timeWindowSize !== undefined} - error={errors.timeWindowSize} - > + <EuiFormRow isInvalid={errors.timeWindowSize.length > 0} error={errors.timeWindowSize}> <EuiFieldNumber data-test-subj="timeWindowSizeNumber" - isInvalid={errors.timeWindowSize.length > 0 && timeWindowSize !== undefined} + isInvalid={errors.timeWindowSize.length > 0} min={0} value={timeWindowSize || ''} onChange={(e) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.tsx index a66d91987e3b2..5259fdf0b5d82 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.tsx @@ -59,6 +59,7 @@ export const ValueExpression = ({ onClick={() => { setValuePopoverOpen(true); }} + isInvalid={errors.length > 0} /> } isOpen={valuePopoverOpen} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_rule_definition.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_definition.tsx new file mode 100644 index 0000000000000..66df2b2ea3b5e --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_definition.tsx @@ -0,0 +1,10 @@ +/* + * 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 { RuleDefinition } from '../application/sections'; +import { RuleDefinitionProps } from '../types'; +export const getRuleDefinitionLazy = (props: RuleDefinitionProps) => <RuleDefinition {...props} />; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts index a19643a8f51bd..c6b8a8376183a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/kibana/kibana_react.mock.ts @@ -18,6 +18,7 @@ import { ActionTypeRegistryContract, AlertsTableConfigurationRegistryContract, } from '../../../types'; +import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; export const createStartServicesMock = (): TriggersAndActionsUiServices => { const core = coreMock.createStart(); @@ -41,6 +42,9 @@ export const createStartServicesMock = (): TriggersAndActionsUiServices => { setBreadcrumbs: jest.fn(), data: dataPluginMock.createStartContract(), dataViews: dataViewPluginMocks.createStartContract(), + dataViewEditor: { + openEditor: jest.fn(), + } as unknown as DataViewEditorStart, unifiedSearch: unifiedSearchPluginMock.createStartContract(), actionTypeRegistry: { has: jest.fn(), diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 12607b4fc2994..a55c1cf5c7c61 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -39,6 +39,7 @@ export type { RuleEventLogListProps, AlertTableFlyoutComponent, GetRenderCellValue, + RuleDefinitionProps, } from './types'; export { diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 6c6e195c5e9bc..b86e2fa465fe2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -38,6 +38,7 @@ import { CreateConnectorFlyoutProps } from './application/sections/action_connec import { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout'; import { getActionFormLazy } from './common/get_action_form'; import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form'; +import { getRuleDefinitionLazy } from './common/get_rule_definition'; import { getRuleStatusPanelLazy } from './common/get_rule_status_panel'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { @@ -105,6 +106,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { getRulesList: () => { return getRulesListLazy({ connectorServices }); }, + getRuleDefinition: (props) => { + return getRuleDefinitionLazy({ ...props, actionTypeRegistry, ruleTypeRegistry }); + }, getRuleStatusPanel: (props) => { return getRuleStatusPanelLazy(props); }, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 3331509befcca..a2882a3a4bfc8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -19,6 +19,7 @@ import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/publi import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public'; import { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; @@ -62,6 +63,7 @@ import type { CreateConnectorFlyoutProps, EditConnectorFlyoutProps, ConnectorServices, + RuleDefinitionProps, } from './types'; import { TriggersActionsUiConfigType } from '../common/types'; import { registerAlertsTableConfiguration } from './application/sections/alerts_table/alerts_page/register_alerts_table_configuration'; @@ -69,6 +71,7 @@ import { PLUGIN_ID } from './common/constants'; import type { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state'; import { getAlertsTableStateLazy } from './common/get_alerts_table_state'; import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form'; +import { getRuleDefinitionLazy } from './common/get_rule_definition'; import { RuleStatusPanelProps } from './application/sections/rule_details/components/rule_status_panel'; export interface TriggersAndActionsUIPublicPluginSetup { @@ -109,6 +112,7 @@ export interface TriggersAndActionsUIPublicPluginStart { props: RulesListNotifyBadgeProps ) => ReactElement<RulesListNotifyBadgeProps>; getRulesList: () => ReactElement; + getRuleDefinition: (props: RuleDefinitionProps) => ReactElement<RuleDefinitionProps>; getRuleStatusPanel: (props: RuleStatusPanelProps) => ReactElement<RuleStatusPanelProps>; } @@ -122,6 +126,7 @@ interface PluginsSetup { interface PluginsStart { data: DataPublicPluginStart; dataViews: DataViewsPublicPluginStart; + dataViewEditor: DataViewEditorStart; charts: ChartsPluginStart; alerting?: AlertingStart; spaces?: SpacesPluginStart; @@ -214,6 +219,7 @@ export class Plugin actions: plugins.actions, data: pluginsStart.data, dataViews: pluginsStart.dataViews, + dataViewEditor: pluginsStart.dataViewEditor, charts: pluginsStart.charts, alerting: pluginsStart.alerting, spaces: pluginsStart.spaces, @@ -323,6 +329,15 @@ export class Plugin getRulesList: () => { return getRulesListLazy({ connectorServices: this.connectorServices! }); }, + getRuleDefinition: ( + props: Omit<RuleDefinitionProps, 'actionTypeRegistry' | 'ruleTypeRegistry'> + ) => { + return getRuleDefinitionLazy({ + ...props, + actionTypeRegistry: this.actionTypeRegistry, + ruleTypeRegistry: this.ruleTypeRegistry, + }); + }, getRuleStatusPanel: (props: RuleStatusPanelProps) => { return getRuleStatusPanelLazy(props); }, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 32ef0d6239d76..5840580ec7006 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -343,6 +343,12 @@ export interface RuleAddProps<MetaData = Record<string, any>> { ruleTypeIndex?: RuleTypeIndex; filteredSolutions?: string[] | undefined; } +export interface RuleDefinitionProps { + rule: Rule; + ruleTypeRegistry: RuleTypeRegistryContract; + actionTypeRegistry: ActionTypeRegistryContract; + onEditRule: () => Promise<void>; +} export enum Percentiles { P50 = 'P50', @@ -413,6 +419,7 @@ export type AlertTableFlyoutComponent = | React.FunctionComponent<AlertsTableFlyoutBaseProps> | React.LazyExoticComponent<ComponentType<AlertsTableFlyoutBaseProps>> | null; + export interface AlertsTableConfigurationRegistry { id: string; columns: EuiDataGridColumn[]; @@ -423,6 +430,10 @@ export interface AlertsTableConfigurationRegistry { }; sort?: SortCombinations[]; getRenderCellValue?: GetRenderCellValue; + useActionsColumn?: () => { + renderCustomActionsRow: (alert?: EcsFieldsResponse) => JSX.Element; + width?: number; + }; } export interface AlertsTableFlyoutBaseProps { diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index a2113a3a230fb..8618be6c9c285 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -19,6 +19,7 @@ { "path": "../features/tsconfig.json" }, { "path": "../rule_registry/tsconfig.json" }, { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/data_view_editor/tsconfig.json" }, { "path": "../../../src/plugins/saved_objects/tsconfig.json" }, { "path": "../../../src/plugins/home/tsconfig.json" }, { "path": "../../../src/plugins/charts/tsconfig.json" }, diff --git a/x-pack/test/accessibility/apps/enterprise_search.ts b/x-pack/test/accessibility/apps/enterprise_search.ts index 0a1a5d68d9621..c2e6c4fa4a5c7 100644 --- a/x-pack/test/accessibility/apps/enterprise_search.ts +++ b/x-pack/test/accessibility/apps/enterprise_search.ts @@ -9,17 +9,21 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const a11y = getService('a11y'); - const esArchiver = getService('esArchiver'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); const { common } = getPageObjects(['common']); + const kibanaServer = getService('kibanaServer'); describe('Enterprise Search Accessibility', () => { // NOTE: These accessibility tests currently only run against Enterprise Search in Kibana // without a sidecar Enterprise Search service/host configured, and as such only test // the basic setup guides and not the full application(s) before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); }); describe('Overview', () => { diff --git a/x-pack/test/accessibility/apps/kibana_overview.ts b/x-pack/test/accessibility/apps/kibana_overview.ts index 19af9c2828d35..373044c4bffc3 100644 --- a/x-pack/test/accessibility/apps/kibana_overview.ts +++ b/x-pack/test/accessibility/apps/kibana_overview.ts @@ -10,17 +10,16 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'home']); const a11y = getService('a11y'); + const kibanaServer = getService('kibanaServer'); describe('Kibana overview Accessibility', () => { - const esArchiver = getService('esArchiver'); - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); await PageObjects.common.navigateToApp('kibanaOverview'); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); }); it('Kibana overview', async () => { diff --git a/x-pack/test/accessibility/apps/login_page.ts b/x-pack/test/accessibility/apps/login_page.ts index 6463e63fb2e49..3993d9ffcd72e 100644 --- a/x-pack/test/accessibility/apps/login_page.ts +++ b/x-pack/test/accessibility/apps/login_page.ts @@ -8,21 +8,21 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const a11y = getService('a11y'); const testSubjects = getService('testSubjects'); const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'security']); + const kibanaServer = getService('kibanaServer'); describe('Security Accessibility', () => { describe('Login Page', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); await PageObjects.security.forceLogout(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); }); afterEach(async () => { diff --git a/x-pack/test/accessibility/apps/spaces.ts b/x-pack/test/accessibility/apps/spaces.ts index 6cc5c2dd73d70..20ce3062c3e02 100644 --- a/x-pack/test/accessibility/apps/spaces.ts +++ b/x-pack/test/accessibility/apps/spaces.ts @@ -13,17 +13,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'spaceSelector', 'home', 'header', 'security']); const a11y = getService('a11y'); const browser = getService('browser'); - const esArchiver = getService('esArchiver'); + const spacesService = getService('spaces'); + const testSubjects = getService('testSubjects'); const retry = getService('retry'); const toasts = getService('toasts'); + const kibanaServer = getService('kibanaServer'); - // Failing: See https://github.com/elastic/kibana/issues/135341 - describe.skip('Kibana Spaces Accessibility', () => { + describe('Kibana Spaces Accessibility', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); await PageObjects.common.navigateToApp('home'); }); + after(async () => { + await spacesService.delete('space_a'); + await kibanaServer.savedObjects.cleanStandardList(); + }); it('a11y test for manage spaces menu from top nav on Kibana home', async () => { await testSubjects.click('space-avatar-default'); diff --git a/x-pack/test/accessibility/apps/tags.ts b/x-pack/test/accessibility/apps/tags.ts index 0afb33ea4765e..da51f2f0535e2 100644 --- a/x-pack/test/accessibility/apps/tags.ts +++ b/x-pack/test/accessibility/apps/tags.ts @@ -16,8 +16,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const toasts = getService('toasts'); - // Failing: See https://github.com/elastic/kibana/issues/135339 - describe.skip('Kibana Tags Page Accessibility', () => { + describe('Kibana Tags Page Accessibility', () => { before(async () => { await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { useActualUrl: true, diff --git a/x-pack/test/accessibility/apps/users.ts b/x-pack/test/accessibility/apps/users.ts index 71ed3a27c1073..6057b4d45bb09 100644 --- a/x-pack/test/accessibility/apps/users.ts +++ b/x-pack/test/accessibility/apps/users.ts @@ -12,14 +12,14 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['security', 'settings']); const a11y = getService('a11y'); - const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const find = getService('find'); const retry = getService('retry'); + const kibanaServer = getService('kibanaServer'); describe('Kibana users Accessibility', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + await kibanaServer.savedObjects.cleanStandardList(); await PageObjects.security.clickElasticsearchUsers(); }); diff --git a/x-pack/test/api_integration/apis/metrics_ui/constants.ts b/x-pack/test/api_integration/apis/metrics_ui/constants.ts index 9ef8c5e6b9407..b37b5b1ddd5da 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/constants.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/constants.ts @@ -27,6 +27,9 @@ export const DATES = { min: new Date('2022-01-18T19:57:47.534Z').getTime(), max: new Date('2022-01-18T20:02:50.043Z').getTime(), }, + rx: { + max: new Date('2022-06-21T17:02:00.00Z').getTime(), + }, logs_and_metrics: { min: 1562786660845, max: 1562786716965, diff --git a/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts index 230ae0e134f43..999a89926cf5c 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/inventory_threshold_alert.ts @@ -136,6 +136,7 @@ export default function ({ getService }: FtrProviderContext) { it('should work FOR LAST 1 minute', async () => { const results = await evaluateCondition({ ...baseOptions, + executionTimestamp: new Date(DATES['8.0.0'].rx.max), condition: { ...baseCondition, metric: 'rx', @@ -155,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) { shouldWarn: false, isNoData: false, isError: false, - currentValue: 1666.6666666666667, + currentValue: 79351.95, }, 'host-1': { metric: 'rx', @@ -168,13 +169,14 @@ export default function ({ getService }: FtrProviderContext) { shouldWarn: false, isNoData: false, isError: false, - currentValue: 2000, + currentValue: 10, }, }); }); it('should work with a long threshold', async () => { const results = await evaluateCondition({ ...baseOptions, + executionTimestamp: new Date(DATES['8.0.0'].rx.max), condition: { ...baseCondition, metric: 'rx', @@ -195,7 +197,7 @@ export default function ({ getService }: FtrProviderContext) { shouldWarn: false, isNoData: false, isError: false, - currentValue: 1666.6666666666667, + currentValue: 79351.95, }, 'host-1': { metric: 'rx', @@ -208,13 +210,14 @@ export default function ({ getService }: FtrProviderContext) { shouldWarn: false, isNoData: false, isError: false, - currentValue: 2000, + currentValue: 10, }, }); }); it('should work FOR LAST 5 minute', async () => { const options = { ...baseOptions, + executionTimestamp: new Date(DATES['8.0.0'].rx.max), condition: { ...baseCondition, metric: 'rx' as SnapshotMetricType, @@ -236,7 +239,7 @@ export default function ({ getService }: FtrProviderContext) { shouldWarn: false, isNoData: false, isError: false, - currentValue: 2266.6666666666665, + currentValue: 125658.70833333333, }, 'host-1': { metric: 'rx', @@ -249,7 +252,7 @@ export default function ({ getService }: FtrProviderContext) { shouldWarn: false, isNoData: false, isError: false, - currentValue: 2266.6666666666665, + currentValue: 11.666666666666668, }, }); }); diff --git a/x-pack/test/api_integration/apis/security_solution/hosts.ts b/x-pack/test/api_integration/apis/security_solution/hosts.ts index 52e1b2eb375ba..a55a019588abd 100644 --- a/x-pack/test/api_integration/apis/security_solution/hosts.ts +++ b/x-pack/test/api_integration/apis/security_solution/hosts.ts @@ -12,7 +12,8 @@ import { HostsFields, HostsStrategyResponse, HostDetailsStrategyResponse, - HostFirstLastSeenStrategyResponse, + FirstLastSeenQuery, + FirstLastSeenStrategyResponse, } from '@kbn/security-solution-plugin/common/search_strategy'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -138,12 +139,13 @@ export default function ({ getService }: FtrProviderContext) { }); it('Make sure that we get First Seen for a Host', async () => { - const firstLastSeenHost = await bsearch.send<HostFirstLastSeenStrategyResponse>({ + const firstLastSeenHost = await bsearch.send<FirstLastSeenStrategyResponse>({ supertest, options: { - factoryQueryType: HostsQueries.firstOrLastSeen, + factoryQueryType: FirstLastSeenQuery, defaultIndex: ['auditbeat-*'], - hostName: 'zeek-sensor-san-francisco', + field: 'host.name', + value: 'zeek-sensor-san-francisco', order: 'asc', }, strategy: 'securitySolutionSearchStrategy', @@ -152,12 +154,13 @@ export default function ({ getService }: FtrProviderContext) { }); it('Make sure that we get Last Seen for a Host', async () => { - const firstLastSeenHost = await bsearch.send<HostFirstLastSeenStrategyResponse>({ + const firstLastSeenHost = await bsearch.send<FirstLastSeenStrategyResponse>({ supertest, options: { - factoryQueryType: HostsQueries.firstOrLastSeen, + factoryQueryType: FirstLastSeenQuery, defaultIndex: ['auditbeat-*'], - hostName: 'zeek-sensor-san-francisco', + field: 'host.name', + value: 'zeek-sensor-san-francisco', order: 'desc', }, strategy: 'securitySolutionSearchStrategy', diff --git a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts index 61a25e3bffaab..e68dcb1c787e8 100644 --- a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts +++ b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts @@ -64,6 +64,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .rate(1) .generator((timestamp) => instance + .containerId(instanceName) .transaction('GET /api/product/list') .timestamp(timestamp) .duration(1000) diff --git a/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts b/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts index d088600c94ab2..79740c616a720 100644 --- a/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details/generate_data.ts @@ -73,6 +73,7 @@ export async function generateData({ .rate(rate) .generator((timestamp) => instance + .containerId('instance-a') .transaction(transaction.name) .timestamp(timestamp) .defaults({ diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index aff63d635c976..6c4f73285e64f 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -82,7 +82,7 @@ export default ({ getService }: FtrProviderContext) => { interval: '5m', rule_id: 'rule-1', language: 'kuery', - output_index: '.siem-signals-default', + output_index: '', max_signals: 100, risk_score: 1, risk_score_mapping: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts index 966420c90b8d2..89b85412f4992 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts @@ -164,7 +164,7 @@ export default ({ getService }: FtrProviderContext) => { interval: '5m', rule_id: 'rule-1', language: 'kuery', - output_index: '.siem-signals-default', + output_index: '', max_signals: 100, risk_score: 1, risk_score_mapping: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/import_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/import_rules.ts index bcbf3c14d27c5..e2ee8395d98c6 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/import_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/import_rules.ts @@ -236,7 +236,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.errors[0]).to.eql({ rule_id: '(unknown id)', error: { - message: 'when "type" is "threshold", "threshold" is required', + message: 'Invalid value "undefined" supplied to "threshold"', status_code: 400, }, }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts index 07ac5ea879bb6..1db5c784660fc 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule.ts @@ -35,7 +35,7 @@ export const getComplexRule = (ruleId = 'rule-1'): Partial<RulesSchema> => ({ enabled: false, index: ['auditbeat-*', 'filebeat-*'], interval: '5m', - output_index: '.siem-signals-default', + output_index: '', meta: { anything_you_want_ui_related_or_otherwise: { as_deep_structured_as_you_need: { diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index 81a169636605b..cc33c2ebff447 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -37,7 +37,7 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial<RulesSchema> => index: ['auditbeat-*', 'filebeat-*'], immutable: false, interval: '5m', - output_index: '.siem-signals-default', + output_index: '', meta: { anything_you_want_ui_related_or_otherwise: { as_deep_structured_as_you_need: { diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index ca8b04e66f3fc..0d6cf9905d4a2 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -24,7 +24,7 @@ export const getSimpleRuleOutput = (ruleId = 'rule-1', enabled = false): Partial interval: '5m', rule_id: ruleId, language: 'kuery', - output_index: '.siem-signals-default', + output_index: '', max_signals: 100, related_integrations: [], required_fields: [], diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json index 71ac4dfc974d4..d69fcacbceede 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/es_archiver/encrypted_saved_objects/data.json @@ -287,46 +287,6 @@ } } -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:5f01fd40-b0b0-11ea-9510-fdf248d5f2a4", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "management", - "minutesOnScreen": 1.60245, - "numberOfClicks": 6, - "timestamp": "2020-06-17T15:36:54.292Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-06-17T15:36:54.292Z" - } - } -} - -{ - "type": "doc", - "value": { - "id": "application_usage_transactional:4ca5ac00-b0b0-11ea-9510-fdf248d5f2a4", - "index": ".kibana_1", - "source": { - "application_usage_transactional": { - "appId": "home", - "minutesOnScreen": 0.4106666666666667, - "numberOfClicks": 3, - "timestamp": "2020-06-17T15:36:23.487Z" - }, - "references": [ - ], - "type": "application_usage_transactional", - "updated_at": "2020-06-17T15:36:23.488Z" - } - } -} - { "type": "doc", "value": { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index d142dc6fd3515..0a135f0d645a3 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -767,6 +767,7 @@ const expectAssetsInstalled = ({ install_started_at: res.attributes.install_started_at, install_source: 'registry', install_format_schema_version: FLEET_INSTALL_FORMAT_VERSION, + verification_status: 'unknown', }); }); }; diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index f31c4cd44b80d..c38e79cc760a2 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -513,6 +513,7 @@ export default function (providerContext: FtrProviderContext) { install_started_at: res.attributes.install_started_at, install_source: 'registry', install_format_schema_version: FLEET_INSTALL_FORMAT_VERSION, + verification_status: 'unknown', }); }); }); diff --git a/x-pack/test/functional/apps/canvas/exports/8.1.embeddable_test.ndjson b/x-pack/test/functional/apps/canvas/exports/8.1.embeddable_test.ndjson new file mode 100644 index 0000000000000..3ec13414a35a1 --- /dev/null +++ b/x-pack/test/functional/apps/canvas/exports/8.1.embeddable_test.ndjson @@ -0,0 +1,6 @@ +{"attributes":{"fieldAttrs":"{\"products.manufacturer\":{\"count\":1},\"products.price\":{\"count\":1},\"products.product_name\":{\"count\":1},\"total_quantity\":{\"count\":1}}","fieldFormatMap":"{\"taxful_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.[00]\"}},\"products.price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"taxless_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.taxless_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.taxful_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.min_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.base_unit_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}},\"products.base_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.00\"}}}","fields":"[]","runtimeFieldMap":"{}","timeFieldName":"order_date","title":"kibana_sample_data_ecommerce","typeMeta":"{}"},"coreMigrationVersion":"8.1.3","id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","migrationVersion":{"index-pattern":"8.0.0"},"references":[],"type":"index-pattern","updated_at":"2022-06-22T13:05:00.290Z","version":"WzE5OCwxXQ=="} +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Agg Viz","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Agg Viz\",\"type\":\"area\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"}],\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":true,\"truncate\":100},\"title\":{\"text\":\"\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"circlesRadius\":1,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"truncateLegend\":true,\"maxLegendLines\":1,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}}}"},"coreMigrationVersion":"8.1.3","id":"21815490-f242-11ec-865d-c9490d79025a","migrationVersion":{"visualization":"8.0.0"},"references":[{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2022-06-22T15:43:58.563Z","version":"WzM4OSwxXQ=="} +{"attributes":{"description":"","state":{"datasourceStates":{"indexpattern":{"layers":{"5228311a-6224-495f-80a7-2e9218844cff":{"columnOrder":["3a9ec66c-f6fd-4a3d-91ab-27b7dc421398","b0b90b4f-e836-461f-863a-80144bf2e011"],"columns":{"3a9ec66c-f6fd-4a3d-91ab-27b7dc421398":{"dataType":"string","isBucketed":true,"label":"Top values of currency","operationType":"terms","params":{"missingBucket":false,"orderBy":{"columnId":"b0b90b4f-e836-461f-863a-80144bf2e011","type":"column"},"orderDirection":"desc","otherBucket":true,"parentFormat":{"id":"terms"},"size":5},"scale":"ordinal","sourceField":"currency"},"b0b90b4f-e836-461f-863a-80144bf2e011":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"___records___"}},"incompleteColumns":{}}}}},"filters":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["b0b90b4f-e836-461f-863a-80144bf2e011"],"layerId":"5228311a-6224-495f-80a7-2e9218844cff","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"3a9ec66c-f6fd-4a3d-91ab-27b7dc421398"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"}}},"title":"demo lens","visualizationType":"lnsXY"},"coreMigrationVersion":"8.1.3","id":"dffc15e0-f22e-11ec-865d-c9490d79025a","migrationVersion":{"lens":"8.1.0"},"references":[{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"indexpattern-datasource-layer-5228311a-6224-495f-80a7-2e9218844cff","type":"index-pattern"}],"type":"lens","updated_at":"2022-06-22T13:26:08.304Z","version":"WzI5MywxXQ=="} +{"attributes":{"description":"","layerListJSON":"[{\"id\":\"0hmz5\",\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"visible\":true,\"style\":{},\"type\":\"EMS_VECTOR_TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"7ameq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"world_countries\",\"tooltipProperties\":[\"name\",\"iso2\"]},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"__kbnjoin__count__741db9c6-8ebb-4ea9-9885-b6b4ac019d14\",\"origin\":\"join\"},\"color\":\"Green to Red\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\",\"joins\":[{\"leftField\":\"iso2\",\"right\":{\"type\":\"ES_TERM_SOURCE\",\"id\":\"741db9c6-8ebb-4ea9-9885-b6b4ac019d14\",\"indexPatternTitle\":\"kibana_sample_data_ecommerce\",\"term\":\"geoip.country_iso_code\",\"indexPatternRefName\":\"layer_1_join_0_index_pattern\",\"metrics\":[{\"type\":\"count\",\"label\":\"sales count\"}],\"applyGlobalQuery\":true}}]},{\"id\":\"jmtgf\",\"label\":\"United States\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"usa_states\",\"tooltipProperties\":[\"name\"]},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"__kbnjoin__count__30a0ec24-49b6-476a-b4ed-6c1636333695\",\"origin\":\"join\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\",\"joins\":[{\"leftField\":\"name\",\"right\":{\"type\":\"ES_TERM_SOURCE\",\"id\":\"30a0ec24-49b6-476a-b4ed-6c1636333695\",\"indexPatternTitle\":\"kibana_sample_data_ecommerce\",\"term\":\"geoip.region_name\",\"indexPatternRefName\":\"layer_2_join_0_index_pattern\",\"metrics\":[{\"type\":\"count\",\"label\":\"sales count\"}],\"applyGlobalQuery\":true}}]},{\"id\":\"ui5f8\",\"label\":\"France\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"france_departments\",\"tooltipProperties\":[\"label_en\"]},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"__kbnjoin__count__e325c9da-73fa-4b3b-8b59-364b99370826\",\"origin\":\"join\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\",\"joins\":[{\"leftField\":\"label_en\",\"right\":{\"type\":\"ES_TERM_SOURCE\",\"id\":\"e325c9da-73fa-4b3b-8b59-364b99370826\",\"indexPatternTitle\":\"kibana_sample_data_ecommerce\",\"term\":\"geoip.region_name\",\"indexPatternRefName\":\"layer_3_join_0_index_pattern\",\"metrics\":[{\"type\":\"count\",\"label\":\"sales count\"}],\"applyGlobalQuery\":true}}]},{\"id\":\"y3fjb\",\"label\":\"United Kingdom\",\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"EMS_FILE\",\"id\":\"uk_subdivisions\",\"tooltipProperties\":[\"label_en\"]},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"__kbnjoin__count__612d805d-8533-43a9-ac0e-cbf51fe63dcd\",\"origin\":\"join\"},\"color\":\"Blues\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\",\"joins\":[{\"leftField\":\"label_en\",\"right\":{\"type\":\"ES_TERM_SOURCE\",\"id\":\"612d805d-8533-43a9-ac0e-cbf51fe63dcd\",\"indexPatternTitle\":\"kibana_sample_data_ecommerce\",\"term\":\"geoip.region_name\",\"indexPatternRefName\":\"layer_4_join_0_index_pattern\",\"metrics\":[{\"type\":\"count\",\"label\":\"sales count\"}],\"applyGlobalQuery\":true}}]},{\"id\":\"c54wk\",\"label\":\"Sales\",\"minZoom\":9,\"maxZoom\":24,\"alpha\":1,\"sourceDescriptor\":{\"id\":\"04c983b0-8cfa-4e6a-a64b-52c10b7008fe\",\"type\":\"ES_SEARCH\",\"geoField\":\"geoip.location\",\"limit\":2048,\"filterByMapBounds\":true,\"tooltipProperties\":[\"category\",\"customer_gender\",\"manufacturer\",\"order_id\",\"total_quantity\",\"total_unique_products\",\"taxful_total_price\",\"order_date\",\"geoip.region_name\",\"geoip.country_iso_code\"],\"indexPatternRefName\":\"layer_5_source_index_pattern\",\"applyGlobalQuery\":true,\"scalingType\":\"LIMIT\"},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"taxful_total_price\",\"origin\":\"source\"},\"color\":\"Greens\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\"},{\"id\":\"qvhh3\",\"label\":\"Total Sales Revenue\",\"minZoom\":0,\"maxZoom\":9,\"alpha\":1,\"sourceDescriptor\":{\"type\":\"ES_GEO_GRID\",\"resolution\":\"COARSE\",\"id\":\"aa7f87b8-9dc5-42be-b19e-1a2fa09b6cad\",\"geoField\":\"geoip.location\",\"requestType\":\"point\",\"metrics\":[{\"type\":\"count\",\"label\":\"sales count\"},{\"type\":\"sum\",\"field\":\"taxful_total_price\",\"label\":\"total sales price\"}],\"indexPatternRefName\":\"layer_6_source_index_pattern\",\"applyGlobalQuery\":true},\"visible\":true,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"doc_count\",\"origin\":\"source\"},\"color\":\"Greens\",\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#cccccc\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"sum_of_taxful_total_price\",\"origin\":\"source\"},\"minSize\":1,\"maxSize\":20,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"labelText\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"sum_of_taxful_total_price\",\"origin\":\"source\"},\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"labelSize\":{\"type\":\"DYNAMIC\",\"options\":{\"field\":{\"name\":\"sum_of_taxful_total_price\",\"origin\":\"source\"},\"minSize\":12,\"maxSize\":24,\"fieldMetaOptions\":{\"isEnabled\":false,\"sigma\":3}}},\"labelBorderSize\":{\"options\":{\"size\":\"MEDIUM\"}},\"symbolizeAs\":{\"options\":{\"value\":\"circle\"}},\"icon\":{\"type\":\"STATIC\",\"options\":{\"value\":\"marker\"}}}},\"type\":\"GEOJSON_VECTOR\"}]","mapStateJSON":"{\"zoom\":2.11,\"center\":{\"lon\":-15.07605,\"lat\":45.88578},\"timeFilters\":{\"from\":\"now-7d\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":0},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"settings\":{\"autoFitToDataBounds\":false}}","title":"[eCommerce] Orders by Country","uiStateJSON":"{\"isDarkMode\":false}"},"coreMigrationVersion":"8.1.3","id":"2c9c1f60-1909-11e9-919b-ffe5949a18d2","migrationVersion":{"map":"8.1.0"},"references":[{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"layer_1_join_0_index_pattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"layer_2_join_0_index_pattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"layer_3_join_0_index_pattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"layer_4_join_0_index_pattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"layer_5_source_index_pattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"layer_6_source_index_pattern","type":"index-pattern"}],"type":"map","updated_at":"2022-06-22T13:05:00.290Z","version":"WzIxNywxXQ=="} +{"attributes":{"@created":"2022-06-23T12:48:05.589Z","@timestamp":"2022-06-23T12:49:52.382Z","assets":{},"colors":["#37988d","#c19628","#b83c6f","#3f9939","#1785b0","#ca5f35","#45bdb0","#f2bc33","#e74b8b","#4fbf48","#1ea6dc","#fd7643","#72cec3","#f5cc5d","#ec77a8","#7acf74","#4cbce4","#fd986f","#a1ded7","#f8dd91","#f2a4c5","#a6dfa2","#86d2ed","#fdba9f","#000000","#444444","#777777","#BBBBBB","#FFFFFF","rgba(255,255,255,0)"],"css":".canvasPage {\n\n}","height":720,"isWriteable":true,"name":"Test Workpad","page":1,"pages":[{"elements":[{"expression":"embeddable \n config=\"eyJ2aWV3TW9kZSI6ImVkaXQiLCJ0aW1lUmFuZ2UiOnsiZnJvbSI6Im5vdy0xNW0iLCJ0byI6Im5vdyJ9LCJkaXNhYmxlVHJpZ2dlcnMiOnRydWUsInJlbmRlck1vZGUiOiJub0ludGVyYWN0aXZpdHkiLCJzYXZlZE9iamVjdElkIjoiMjE4MTU0OTAtZjI0Mi0xMWVjLTg2NWQtYzk0OTBkNzkwMjVhIiwiZmlsdGVycyI6W10sImlkIjoiZWxlbWVudC1jZWI4OTRkNC04MGI0LTRhZmQtOWUwOS0wZGEyOTY4NDExNDcifQ==\" type=\"visualization\"\n| render","filter":null,"id":"element-ceb894d4-80b4-4afd-9e09-0da296841147","position":{"angle":0,"height":300,"left":20,"parent":null,"top":20,"width":500}},{"expression":"embeddable \n config=\"eyJ2aWV3TW9kZSI6ImVkaXQiLCJ0aW1lUmFuZ2UiOnsiZnJvbSI6Im5vdy0xNW0iLCJ0byI6Im5vdyJ9LCJkaXNhYmxlVHJpZ2dlcnMiOnRydWUsInJlbmRlck1vZGUiOiJub0ludGVyYWN0aXZpdHkiLCJzYXZlZE9iamVjdElkIjoiZGZmYzE1ZTAtZjIyZS0xMWVjLTg2NWQtYzk0OTBkNzkwMjVhIiwiZmlsdGVycyI6W10sImlkIjoiZWxlbWVudC1iNzJjYmJlNC1lZWVhLTRiYTMtODhiOC1mZTc3N2M2MDc2ODYifQ==\" type=\"lens\"\n| render","filter":null,"id":"element-b72cbbe4-eeea-4ba3-88b8-fe777c607686","position":{"angle":0,"height":300,"left":27,"parent":null,"top":336,"width":500}},{"expression":"embeddable \n config=\"eyJ2aWV3TW9kZSI6ImVkaXQiLCJ0aW1lUmFuZ2UiOnsiZnJvbSI6Im5vdy0xNW0iLCJ0byI6Im5vdyJ9LCJkaXNhYmxlVHJpZ2dlcnMiOnRydWUsInJlbmRlck1vZGUiOiJub0ludGVyYWN0aXZpdHkiLCJzYXZlZE9iamVjdElkIjoiMmM5YzFmNjAtMTkwOS0xMWU5LTkxOWItZmZlNTk0OWExOGQyIiwiZmlsdGVycyI6W10sImlkIjoiZWxlbWVudC03ZTU3NTJhNC01NjQzLTRmYWUtYTg5ZS1hZWI5MjViZjlkMmIiLCJtYXBDZW50ZXIiOnsibGF0Ijo0NS44ODU3OCwibG9uIjotMTUuMDc2MDUsInpvb20iOjIuMTF9LCJtYXBCdWZmZXIiOnsibWluTG9uIjotOTAsIm1pbkxhdCI6MCwibWF4TG9uIjo0NSwibWF4TGF0Ijo2Ni41MTMyNn0sImlzTGF5ZXJUT0NPcGVuIjp0cnVlLCJvcGVuVE9DRGV0YWlscyI6W10sImhpZGRlbkxheWVycyI6W119\" type=\"map\"\n| render","filter":null,"id":"element-7e5752a4-5643-4fae-a89e-aeb925bf9d2b","position":{"angle":0,"height":291,"left":566,"parent":null,"top":150,"width":364}}],"groups":[],"id":"page-6bc85300-159a-4246-ad13-e967bb80b25f","style":{"background":"#FFF"},"transition":{}},{"elements":[{"expression":"embeddable \n config=\"eyJ2aWV3TW9kZSI6ImVkaXQiLCJ0aW1lUmFuZ2UiOnsiZnJvbSI6Im5vdy0xNW0iLCJ0byI6Im5vdyJ9LCJkaXNhYmxlVHJpZ2dlcnMiOnRydWUsInJlbmRlck1vZGUiOiJub0ludGVyYWN0aXZpdHkiLCJhdHRyaWJ1dGVzIjp7InRpdGxlIjoiIiwidmlzdWFsaXphdGlvblR5cGUiOiJsbnNYWSIsInR5cGUiOiJsZW5zIiwicmVmZXJlbmNlcyI6W3sidHlwZSI6ImluZGV4LXBhdHRlcm4iLCJpZCI6ImZmOTU5ZDQwLWI4ODAtMTFlOC1hNmQ5LWU1NDZmZTJiYmE1ZiIsIm5hbWUiOiJpbmRleHBhdHRlcm4tZGF0YXNvdXJjZS1jdXJyZW50LWluZGV4cGF0dGVybiJ9LHsidHlwZSI6ImluZGV4LXBhdHRlcm4iLCJpZCI6ImZmOTU5ZDQwLWI4ODAtMTFlOC1hNmQ5LWU1NDZmZTJiYmE1ZiIsIm5hbWUiOiJpbmRleHBhdHRlcm4tZGF0YXNvdXJjZS1sYXllci0wYTdhMDU3ZS04YzA5LTQyMTQtYjJiNy1mOTRlNmU1MjMwYjgifV0sInN0YXRlIjp7InZpc3VhbGl6YXRpb24iOnsibGVnZW5kIjp7ImlzVmlzaWJsZSI6dHJ1ZSwicG9zaXRpb24iOiJyaWdodCJ9LCJ2YWx1ZUxhYmVscyI6ImhpZGUiLCJmaXR0aW5nRnVuY3Rpb24iOiJOb25lIiwieUxlZnRFeHRlbnQiOnsibW9kZSI6ImZ1bGwifSwieVJpZ2h0RXh0ZW50Ijp7Im1vZGUiOiJmdWxsIn0sImF4aXNUaXRsZXNWaXNpYmlsaXR5U2V0dGluZ3MiOnsieCI6dHJ1ZSwieUxlZnQiOnRydWUsInlSaWdodCI6dHJ1ZX0sInRpY2tMYWJlbHNWaXNpYmlsaXR5U2V0dGluZ3MiOnsieCI6dHJ1ZSwieUxlZnQiOnRydWUsInlSaWdodCI6dHJ1ZX0sImxhYmVsc09yaWVudGF0aW9uIjp7IngiOjAsInlMZWZ0IjowLCJ5UmlnaHQiOjB9LCJncmlkbGluZXNWaXNpYmlsaXR5U2V0dGluZ3MiOnsieCI6dHJ1ZSwieUxlZnQiOnRydWUsInlSaWdodCI6dHJ1ZX0sInByZWZlcnJlZFNlcmllc1R5cGUiOiJiYXJfc3RhY2tlZCIsImxheWVycyI6W3sibGF5ZXJJZCI6IjBhN2EwNTdlLThjMDktNDIxNC1iMmI3LWY5NGU2ZTUyMzBiOCIsImFjY2Vzc29ycyI6WyJmZjdmODczMy0zODEwLTQzYjctOWMxZS05ZjBmZTZlY2ZjNGUiXSwicG9zaXRpb24iOiJ0b3AiLCJzZXJpZXNUeXBlIjoiYmFyX3N0YWNrZWQiLCJzaG93R3JpZGxpbmVzIjpmYWxzZSwibGF5ZXJUeXBlIjoiZGF0YSIsInhBY2Nlc3NvciI6Ijg4MTM0MDcwLTJlNGItNGVjZi1hMTUwLTgzMGFmNWMzOTAzZCJ9XX0sInF1ZXJ5Ijp7InF1ZXJ5IjoiIiwibGFuZ3VhZ2UiOiJrdWVyeSJ9LCJmaWx0ZXJzIjpbXSwiZGF0YXNvdXJjZVN0YXRlcyI6eyJpbmRleHBhdHRlcm4iOnsibGF5ZXJzIjp7IjBhN2EwNTdlLThjMDktNDIxNC1iMmI3LWY5NGU2ZTUyMzBiOCI6eyJjb2x1bW5zIjp7Ijg4MTM0MDcwLTJlNGItNGVjZi1hMTUwLTgzMGFmNWMzOTAzZCI6eyJsYWJlbCI6IlRvcCB2YWx1ZXMgb2YgY3VycmVuY3kiLCJkYXRhVHlwZSI6InN0cmluZyIsIm9wZXJhdGlvblR5cGUiOiJ0ZXJtcyIsInNjYWxlIjoib3JkaW5hbCIsInNvdXJjZUZpZWxkIjoiY3VycmVuY3kiLCJpc0J1Y2tldGVkIjp0cnVlLCJwYXJhbXMiOnsic2l6ZSI6NSwib3JkZXJCeSI6eyJ0eXBlIjoiY29sdW1uIiwiY29sdW1uSWQiOiJmZjdmODczMy0zODEwLTQzYjctOWMxZS05ZjBmZTZlY2ZjNGUifSwib3JkZXJEaXJlY3Rpb24iOiJkZXNjIiwib3RoZXJCdWNrZXQiOnRydWUsIm1pc3NpbmdCdWNrZXQiOmZhbHNlLCJwYXJlbnRGb3JtYXQiOnsiaWQiOiJ0ZXJtcyJ9fX0sImZmN2Y4NzMzLTM4MTAtNDNiNy05YzFlLTlmMGZlNmVjZmM0ZSI6eyJsYWJlbCI6IkNvdW50IG9mIHJlY29yZHMiLCJkYXRhVHlwZSI6Im51bWJlciIsIm9wZXJhdGlvblR5cGUiOiJjb3VudCIsImlzQnVja2V0ZWQiOmZhbHNlLCJzY2FsZSI6InJhdGlvIiwic291cmNlRmllbGQiOiJfX19yZWNvcmRzX19fIn19LCJjb2x1bW5PcmRlciI6WyI4ODEzNDA3MC0yZTRiLTRlY2YtYTE1MC04MzBhZjVjMzkwM2QiLCJmZjdmODczMy0zODEwLTQzYjctOWMxZS05ZjBmZTZlY2ZjNGUiXSwiaW5jb21wbGV0ZUNvbHVtbnMiOnt9fX19fX19LCJmaWx0ZXJzIjpbXSwiaWQiOiJlbGVtZW50LTVmYzhhZmI3LTFhN2QtNDdlYS04ZjY2LWZhNDBlMjY5MzI4MCIsImVuaGFuY2VtZW50cyI6e319\" type=\"lens\"\n| render","filter":null,"id":"element-5fc8afb7-1a7d-47ea-8f66-fa40e2693280","position":{"angle":0,"height":300,"left":20,"parent":null,"top":20,"width":500}},{"expression":"embeddable \n config=\"eyJ2aWV3TW9kZSI6ImVkaXQiLCJ0aW1lUmFuZ2UiOnsiZnJvbSI6Im5vdy0xNW0iLCJ0byI6Im5vdyJ9LCJkaXNhYmxlVHJpZ2dlcnMiOnRydWUsInJlbmRlck1vZGUiOiJub0ludGVyYWN0aXZpdHkiLCJhdHRyaWJ1dGVzIjp7InRpdGxlIjoiIiwiZGVzY3JpcHRpb24iOiIiLCJsYXllckxpc3RKU09OIjoiW3tcInNvdXJjZURlc2NyaXB0b3JcIjp7XCJ0eXBlXCI6XCJFTVNfVE1TXCIsXCJpc0F1dG9TZWxlY3RcIjp0cnVlLFwibGlnaHRNb2RlRGVmYXVsdFwiOlwicm9hZF9tYXBfZGVzYXR1cmF0ZWRcIn0sXCJpZFwiOlwiNDMwYTVmZjAtYmM4Yi00MjdlLWE3ODYtODk1ZDIyMDVmNGU3XCIsXCJsYWJlbFwiOm51bGwsXCJtaW5ab29tXCI6MCxcIm1heFpvb21cIjoyNCxcImFscGhhXCI6MSxcInZpc2libGVcIjp0cnVlLFwic3R5bGVcIjp7XCJ0eXBlXCI6XCJUSUxFXCJ9LFwiaW5jbHVkZUluRml0VG9Cb3VuZHNcIjp0cnVlLFwidHlwZVwiOlwiRU1TX1ZFQ1RPUl9USUxFXCJ9LHtcInNvdXJjZURlc2NyaXB0b3JcIjp7XCJnZW9GaWVsZFwiOlwiZ2VvaXAubG9jYXRpb25cIixcInNjYWxpbmdUeXBlXCI6XCJNVlRcIixcImlkXCI6XCI4YWRhOTk2MS1lZWQ2LTQ3MzYtYmRkMC05OWI2ZGRmNWJiOTBcIixcInR5cGVcIjpcIkVTX1NFQVJDSFwiLFwiYXBwbHlHbG9iYWxRdWVyeVwiOnRydWUsXCJhcHBseUdsb2JhbFRpbWVcIjp0cnVlLFwiYXBwbHlGb3JjZVJlZnJlc2hcIjp0cnVlLFwiZmlsdGVyQnlNYXBCb3VuZHNcIjp0cnVlLFwidG9vbHRpcFByb3BlcnRpZXNcIjpbXSxcInNvcnRGaWVsZFwiOlwiXCIsXCJzb3J0T3JkZXJcIjpcImRlc2NcIixcInRvcEhpdHNTcGxpdEZpZWxkXCI6XCJcIixcInRvcEhpdHNTaXplXCI6MSxcImluZGV4UGF0dGVyblJlZk5hbWVcIjpcImxheWVyXzFfc291cmNlX2luZGV4X3BhdHRlcm5cIn0sXCJpZFwiOlwiYTNlYzYzZjAtMzViNy00Y2JkLTk0MGYtNzZiZGM2YjRjNThmXCIsXCJsYWJlbFwiOlwic29tZXRoaW5nXCIsXCJtaW5ab29tXCI6MCxcIm1heFpvb21cIjoyNCxcImFscGhhXCI6MC43NSxcInZpc2libGVcIjp0cnVlLFwic3R5bGVcIjp7XCJ0eXBlXCI6XCJWRUNUT1JcIixcInByb3BlcnRpZXNcIjp7XCJpY29uXCI6e1widHlwZVwiOlwiU1RBVElDXCIsXCJvcHRpb25zXCI6e1widmFsdWVcIjpcIm1hcmtlclwifX0sXCJmaWxsQ29sb3JcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJjb2xvclwiOlwiIzU0QjM5OVwifX0sXCJsaW5lQ29sb3JcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJjb2xvclwiOlwiIzQxOTM3Y1wifX0sXCJsaW5lV2lkdGhcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJzaXplXCI6MX19LFwiaWNvblNpemVcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJzaXplXCI6Nn19LFwiaWNvbk9yaWVudGF0aW9uXCI6e1widHlwZVwiOlwiU1RBVElDXCIsXCJvcHRpb25zXCI6e1wib3JpZW50YXRpb25cIjowfX0sXCJsYWJlbFRleHRcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJ2YWx1ZVwiOlwiXCJ9fSxcImxhYmVsQ29sb3JcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJjb2xvclwiOlwiIzAwMDAwMFwifX0sXCJsYWJlbFNpemVcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJzaXplXCI6MTR9fSxcImxhYmVsQm9yZGVyQ29sb3JcIjp7XCJ0eXBlXCI6XCJTVEFUSUNcIixcIm9wdGlvbnNcIjp7XCJjb2xvclwiOlwiI0ZGRkZGRlwifX0sXCJzeW1ib2xpemVBc1wiOntcIm9wdGlvbnNcIjp7XCJ2YWx1ZVwiOlwiY2lyY2xlXCJ9fSxcImxhYmVsQm9yZGVyU2l6ZVwiOntcIm9wdGlvbnNcIjp7XCJzaXplXCI6XCJTTUFMTFwifX19LFwiaXNUaW1lQXdhcmVcIjp0cnVlfSxcImluY2x1ZGVJbkZpdFRvQm91bmRzXCI6dHJ1ZSxcInR5cGVcIjpcIk1WVF9WRUNUT1JcIixcImpvaW5zXCI6W119XSIsIm1hcFN0YXRlSlNPTiI6IntcInpvb21cIjoxLjQ2LFwiY2VudGVyXCI6e1wibG9uXCI6MCxcImxhdFwiOjE5Ljk0Mjc3fSxcInRpbWVGaWx0ZXJzXCI6e1wiZnJvbVwiOlwibm93LTE1bVwiLFwidG9cIjpcIm5vd1wifSxcInJlZnJlc2hDb25maWdcIjp7XCJpc1BhdXNlZFwiOnRydWUsXCJpbnRlcnZhbFwiOjB9LFwicXVlcnlcIjp7XCJxdWVyeVwiOlwiXCIsXCJsYW5ndWFnZVwiOlwia3VlcnlcIn0sXCJmaWx0ZXJzXCI6W10sXCJzZXR0aW5nc1wiOntcImF1dG9GaXRUb0RhdGFCb3VuZHNcIjpmYWxzZSxcImJhY2tncm91bmRDb2xvclwiOlwiI2ZmZmZmZlwiLFwiZGlzYWJsZUludGVyYWN0aXZlXCI6ZmFsc2UsXCJkaXNhYmxlVG9vbHRpcENvbnRyb2xcIjpmYWxzZSxcImhpZGVUb29sYmFyT3ZlcmxheVwiOmZhbHNlLFwiaGlkZUxheWVyQ29udHJvbFwiOmZhbHNlLFwiaGlkZVZpZXdDb250cm9sXCI6ZmFsc2UsXCJpbml0aWFsTG9jYXRpb25cIjpcIkxBU1RfU0FWRURfTE9DQVRJT05cIixcImZpeGVkTG9jYXRpb25cIjp7XCJsYXRcIjowLFwibG9uXCI6MCxcInpvb21cIjoyfSxcImJyb3dzZXJMb2NhdGlvblwiOntcInpvb21cIjoyfSxcIm1heFpvb21cIjoyNCxcIm1pblpvb21cIjowLFwic2hvd1NjYWxlQ29udHJvbFwiOmZhbHNlLFwic2hvd1NwYXRpYWxGaWx0ZXJzXCI6dHJ1ZSxcInNob3dUaW1lc2xpZGVyVG9nZ2xlQnV0dG9uXCI6dHJ1ZSxcInNwYXRpYWxGaWx0ZXJzQWxwYVwiOjAuMyxcInNwYXRpYWxGaWx0ZXJzRmlsbENvbG9yXCI6XCIjREE4QjQ1XCIsXCJzcGF0aWFsRmlsdGVyc0xpbmVDb2xvclwiOlwiI0RBOEI0NVwifX0iLCJ1aVN0YXRlSlNPTiI6IntcImlzTGF5ZXJUT0NPcGVuXCI6dHJ1ZSxcIm9wZW5UT0NEZXRhaWxzXCI6W119In0sImZpbHRlcnMiOltdLCJpZCI6ImVsZW1lbnQtMDE4MzM3YzctOWE3OS00MTkwLWExMWUtMmY4ZDc1Y2Y0NWYzIiwibWFwQ2VudGVyIjp7ImxhdCI6MTkuOTQyNzcsImxvbiI6MCwiem9vbSI6MS40Nn0sIm1hcEJ1ZmZlciI6eyJtaW5Mb24iOi05MCwibWluTGF0IjotNjYuNTEzMjYsIm1heExvbiI6OTAsIm1heExhdCI6NjYuNTEzMjZ9LCJpc0xheWVyVE9DT3BlbiI6dHJ1ZSwib3BlblRPQ0RldGFpbHMiOltdLCJoaWRkZW5MYXllcnMiOltdLCJlbmhhbmNlbWVudHMiOnt9fQ==\" type=\"map\"\n| render","filter":null,"id":"element-018337c7-9a79-4190-a11e-2f8d75cf45f3","position":{"angle":0,"height":300,"left":430,"parent":null,"top":360,"width":500}}],"groups":[],"id":"page-b8904ba9-0d34-4d67-9dc4-2e24d6dd08a8","style":{"background":"#FFF"},"transition":{}}],"variables":[],"width":1080},"coreMigrationVersion":"8.1.3","id":"workpad-a57ed7c6-d7db-4b4e-9b34-a46a33f6a3aa","migrationVersion":{"canvas-workpad":"8.1.0"},"references":[{"id":"21815490-f242-11ec-865d-c9490d79025a","name":"element-ceb894d4-80b4-4afd-9e09-0da296841147:l0_embeddable.savedObjectId","type":"visualization"},{"id":"dffc15e0-f22e-11ec-865d-c9490d79025a","name":"element-b72cbbe4-eeea-4ba3-88b8-fe777c607686:l0_embeddable.savedObjectId","type":"lens"},{"id":"2c9c1f60-1909-11e9-919b-ffe5949a18d2","name":"element-7e5752a4-5643-4fae-a89e-aeb925bf9d2b:l0_embeddable.savedObjectId","type":"map"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"element-5fc8afb7-1a7d-47ea-8f66-fa40e2693280:l0_indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"element-5fc8afb7-1a7d-47ea-8f66-fa40e2693280:l0_indexpattern-datasource-layer-0a7a057e-8c09-4214-b2b7-f94e6e5230b8","type":"index-pattern"},{"id":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","name":"element-018337c7-9a79-4190-a11e-2f8d75cf45f3:l0_layer_1_source_index_pattern","type":"index-pattern"}],"type":"canvas-workpad","updated_at":"2022-06-23T12:49:52.425Z","version":"WzU4OSwxXQ=="} +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":5,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts b/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts index 12a75e5a2ba8b..7577073a1004d 100644 --- a/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts +++ b/x-pack/test/functional/apps/canvas/migrations_smoke_test.ts @@ -15,10 +15,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('imports an 8.2 workpad', async function () { /* In 8.1 Canvas introduced by value embeddables, which requires expressions to know about embeddable migrations - Starting in 8.3, we were seeing an error during migration where it would appear that an 8.2 workpad was - from a future version. This was because there were missing embeddable migrations on the expression because + Starting in 8.3, we were seeing an error during migration where it would appear that an 8.2 workpad was + from a future version. This was because there were missing embeddable migrations on the expression because the Canvas plugin was adding the embeddable expression with all of it's migrations before other embeddables had - registered their own migrations. + registered their own migrations. This smoke test is intended to import an 8.2 workpad to ensure that we don't hit a similar scenario in the future */ @@ -31,5 +31,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.savedObjects.checkImportSucceeded(); await PageObjects.savedObjects.clickImportDone(); }); + + it('migrates a workpad from 8.1', async function () { + /* + This is a smoke test to make sure migrations don't fail. + This workpad from 8.1 has both by-val and by-ref embeddables + */ + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.savedObjects.waitTableIsLoaded(); + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', '8.1.embeddable_test.ndjson') + ); + await PageObjects.savedObjects.checkImportSucceeded(); + await PageObjects.savedObjects.clickImportDone(); + }); }); } diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 354e13f960192..c9a643c4c3048 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -90,6 +90,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { } ); await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('logstash-*'); }); after(async () => { @@ -230,6 +231,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it(`doesn't show visualize button`, async () => { await PageObjects.common.navigateToApp('discover'); await PageObjects.common.waitForTopNavToBeVisible(); + await PageObjects.discover.selectIndexPattern('logstash-*'); await setDiscoverTimeRange(); await PageObjects.discover.clickFieldListItem('bytes'); await PageObjects.discover.expectMissingFieldListItemVisualize('bytes'); diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index 6410a0b0272f8..569441d26271b 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['settings', 'common', 'graph', 'header']); + const kibanaServer = getService('kibanaServer'); const log = getService('log'); const esArchiver = getService('esArchiver'); const browser = getService('browser'); @@ -28,6 +29,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.graph.createWorkspace(); }); + after(async () => { + await kibanaServer.savedObjects.clean({ types: ['index-pattern'] }); + + await esArchiver.unload('x-pack/test/functional/es_archives/graph/secrepo'); + }); + const graphName = 'my Graph workspace name ' + new Date().getTime(); const expectedNodes = [ diff --git a/x-pack/test/functional/apps/lens/group2/index.ts b/x-pack/test/functional/apps/lens/group2/index.ts index 0e1c732dff41c..f63fc0ecebca2 100644 --- a/x-pack/test/functional/apps/lens/group2/index.ts +++ b/x-pack/test/functional/apps/lens/group2/index.ts @@ -72,7 +72,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./add_to_dashboard')); loadTestFile(require.resolve('./runtime_fields')); loadTestFile(require.resolve('./dashboard')); - loadTestFile(require.resolve('./multi_terms')); + loadTestFile(require.resolve('./terms')); loadTestFile(require.resolve('./epoch_millis')); loadTestFile(require.resolve('./show_underlying_data')); loadTestFile(require.resolve('./show_underlying_data_dashboard')); diff --git a/x-pack/test/functional/apps/lens/group2/multi_terms.ts b/x-pack/test/functional/apps/lens/group2/multi_terms.ts deleted file mode 100644 index 58fa172378964..0000000000000 --- a/x-pack/test/functional/apps/lens/group2/multi_terms.ts +++ /dev/null @@ -1,90 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); - const elasticChart = getService('elasticChart'); - - describe('lens multi terms suite', () => { - it('should allow creation of lens xy chart with multi terms categories', async () => { - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.clickVisType('lens'); - await elasticChart.setNewChartUiDebugFlag(true); - await PageObjects.lens.goToTimeRange(); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'average', - field: 'bytes', - }); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'terms', - field: 'geo.src', - keepOpen: true, - }); - - await PageObjects.lens.addTermToAgg('geo.dest'); - - await PageObjects.lens.closeDimensionEditor(); - - expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( - 'Top values of geo.src + 1 other' - ); - - await PageObjects.lens.openDimensionEditor('lnsXY_xDimensionPanel'); - - await PageObjects.lens.addTermToAgg('bytes'); - - await PageObjects.lens.closeDimensionEditor(); - - expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( - 'Top values of geo.src + 2 others' - ); - - const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); - expect(data!.bars![0].bars[0].x).to.eql('PE › US › 19,986'); - }); - - it('should allow creation of lens xy chart with multi terms categories split', async () => { - await PageObjects.lens.removeDimension('lnsXY_xDimensionPanel'); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', - operation: 'terms', - field: 'geo.src', - keepOpen: true, - }); - - await PageObjects.lens.addTermToAgg('geo.dest'); - await PageObjects.lens.addTermToAgg('bytes'); - - await PageObjects.lens.closeDimensionEditor(); - - const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); - expect(data?.bars?.[0]?.name).to.eql('PE › US › 19,986'); - }); - - it('should not show existing defined fields for new term', async () => { - await PageObjects.lens.openDimensionEditor('lnsXY_splitDimensionPanel'); - - await PageObjects.lens.checkTermsAreNotAvailableToAgg(['bytes', 'geo.src', 'geo.dest']); - - await PageObjects.lens.closeDimensionEditor(); - }); - }); -} diff --git a/x-pack/test/functional/apps/lens/group2/terms.ts b/x-pack/test/functional/apps/lens/group2/terms.ts new file mode 100644 index 0000000000000..3b93c39eb7a6b --- /dev/null +++ b/x-pack/test/functional/apps/lens/group2/terms.ts @@ -0,0 +1,156 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const elasticChart = getService('elasticChart'); + const testSubjects = getService('testSubjects'); + const comboBox = getService('comboBox'); + const find = getService('find'); + const retry = getService('retry'); + + describe('lens terms', () => { + describe('lens multi terms suite', () => { + it('should allow creation of lens xy chart with multi terms categories', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + keepOpen: true, + }); + + await PageObjects.lens.addTermToAgg('geo.dest'); + + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( + 'Top values of geo.src + 1 other' + ); + + await PageObjects.lens.openDimensionEditor('lnsXY_xDimensionPanel'); + + await PageObjects.lens.addTermToAgg('bytes'); + + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( + 'Top values of geo.src + 2 others' + ); + + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data!.bars![0].bars[0].x).to.eql('PE › US › 19,986'); + }); + + it('should allow creation of lens xy chart with multi terms categories split', async () => { + await PageObjects.lens.removeDimension('lnsXY_xDimensionPanel'); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + keepOpen: true, + }); + + await PageObjects.lens.addTermToAgg('geo.dest'); + await PageObjects.lens.addTermToAgg('bytes'); + + await PageObjects.lens.closeDimensionEditor(); + + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.bars?.[0]?.name).to.eql('PE › US › 19,986'); + }); + + it('should not show existing defined fields for new term', async () => { + await PageObjects.lens.openDimensionEditor('lnsXY_splitDimensionPanel'); + + await PageObjects.lens.checkTermsAreNotAvailableToAgg(['bytes', 'geo.src', 'geo.dest']); + + await PageObjects.lens.closeDimensionEditor(); + }); + }); + describe('sorting by custom metric', () => { + it('should allow sort by custom metric', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + keepOpen: true, + }); + await find.clickByCssSelector( + 'select[data-test-subj="indexPattern-terms-orderBy"] > option[value="custom"]' + ); + + const fnTarget = await testSubjects.find('indexPattern-reference-function'); + await comboBox.openOptionsList(fnTarget); + await comboBox.setElement(fnTarget, 'percentile'); + + const fieldTarget = await testSubjects.find( + 'indexPattern-reference-field-selection-row>indexPattern-dimension-field' + ); + await comboBox.openOptionsList(fieldTarget); + await comboBox.setElement(fieldTarget, 'bytes'); + + await retry.try(async () => { + // Can not use testSubjects because data-test-subj is placed range input and number input + const percentileInput = await find.byCssSelector( + `input[data-test-subj="lns-indexPattern-percentile-input"][type='number']` + ); + await percentileInput.click(); + await percentileInput.clearValue(); + await percentileInput.type('60'); + + const percentileValue = await percentileInput.getAttribute('value'); + if (percentileValue !== '60') { + throw new Error('layerPanelTopHitsSize not set to 60'); + } + }); + + await PageObjects.lens.waitForVisualization('xyVisChart'); + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel', 0)).to.eql( + 'Top 5 values of geo.src' + ); + + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data!.bars![0].bars[0].x).to.eql('BN'); + expect(data!.bars![0].bars[0].y).to.eql(19265); + }); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz b/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz index 217442c2cf338..78dd0e435db16 100644 Binary files a/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz and b/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/mappings.json b/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/mappings.json index 278f5bb0c090a..60c425a3b19a4 100644 --- a/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/mappings.json +++ b/x-pack/test/functional/es_archives/infra/8.0.0/hosts_only/mappings.json @@ -44,9 +44,189 @@ }, "host": { "properties": { + "architecture": { + "type": "keyword", + "ignore_above": 1024 + }, + "containerized": { + "type": "boolean" + }, + "cpu": { + "properties": { + "usage": { + "type": "scaled_float", + "scaling_factor": 1000 + } + } + }, + "disk": { + "properties": { + "read": { + "properties": { + "bytes": { + "type": "long" + } + } + }, + "write": { + "properties": { + "bytes": { + "type": "long" + } + } + } + } + }, + "domain": { + "type": "keyword", + "ignore_above": 1024 + }, + "geo": { + "properties": { + "city_name": { + "type": "keyword", + "ignore_above": 1024 + }, + "continent_code": { + "type": "keyword", + "ignore_above": 1024 + }, + "continent_name": { + "type": "keyword", + "ignore_above": 1024 + }, + "country_iso_code": { + "type": "keyword", + "ignore_above": 1024 + }, + "country_name": { + "type": "keyword", + "ignore_above": 1024 + }, + "location": { + "type": "geo_point" + }, + "name": { + "type": "keyword", + "ignore_above": 1024 + }, + "postal_code": { + "type": "keyword", + "ignore_above": 1024 + }, + "region_iso_code": { + "type": "keyword", + "ignore_above": 1024 + }, + "region_name": { + "type": "keyword", + "ignore_above": 1024 + }, + "timezone": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "hostname": { + "type": "keyword", + "ignore_above": 1024 + }, + "id": { + "type": "keyword", + "ignore_above": 1024 + }, + "ip": { + "type": "ip" + }, + "mac": { + "type": "keyword", + "ignore_above": 1024 + }, "name": { - "ignore_above": 256, - "type": "keyword" + "type": "keyword", + "ignore_above": 1024 + }, + "network": { + "properties": { + "egress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + }, + "ingress": { + "properties": { + "bytes": { + "type": "long" + }, + "packets": { + "type": "long" + } + } + } + } + }, + "os": { + "properties": { + "build": { + "type": "keyword", + "ignore_above": 1024 + }, + "codename": { + "type": "keyword", + "ignore_above": 1024 + }, + "family": { + "type": "keyword", + "ignore_above": 1024 + }, + "full": { + "type": "keyword", + "ignore_above": 1024, + "fields": { + "text": { + "type": "match_only_text" + } + } + }, + "kernel": { + "type": "keyword", + "ignore_above": 1024 + }, + "name": { + "type": "keyword", + "ignore_above": 1024, + "fields": { + "text": { + "type": "match_only_text" + } + } + }, + "platform": { + "type": "keyword", + "ignore_above": 1024 + }, + "type": { + "type": "keyword", + "ignore_above": 1024 + }, + "version": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, + "type": { + "type": "keyword", + "ignore_above": 1024 + }, + "uptime": { + "type": "long" } } }, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 469c9762611a8..81a1dc109b562 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -20,7 +20,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const browser = getService('browser'); const endpointTestResources = getService('endpointTestResources'); - const policyTestResources = getService('policyTestResources'); const expectedData = [ [ @@ -72,8 +71,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return tableData; }; - // Failing: See https://github.com/elastic/kibana/issues/114249 - describe.skip('endpoint list', function () { + describe('endpoint list', function () { const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms)); let indexedData: IndexedHostsAndAlertsResponse; describe('when initially navigating to page', () => { @@ -90,8 +88,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('when there is data,', () => { before(async () => { - const endpointPackage = await policyTestResources.getEndpointPackage(); - await endpointTestResources.setMetadataTransformFrequency('1s', endpointPackage.version); indexedData = await endpointTestResources.loadEndpointData({ numHosts: 3 }); await pageObjects.endpoint.navigateToEndpointList(); await pageObjects.endpoint.waitForTableToHaveNumberOfEntries('endpointListTable', 3, 90000); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts index ccfccd8ce95a9..cdf22b9e43f5e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts @@ -79,8 +79,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await backButton.click(); await pageObjects.policy.ensureIsOnListPage(); }); - // FLAKY: https://github.com/elastic/kibana/issues/131602 - describe.skip('when the endpoint count link is clicked', () => { + describe('when the endpoint count link is clicked', () => { it('navigates to the endpoint list page filtered by policy', async () => { const endpointCount = (await testSubjects.findAll('policyEndpointCountLink'))[0]; await endpointCount.click(); diff --git a/x-pack/test/security_solution_endpoint/services/endpoint.ts b/x-pack/test/security_solution_endpoint/services/endpoint.ts index d526c59ee6864..50e0b0821ec81 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint.ts @@ -23,6 +23,7 @@ import { GetTransformsResponseSchema } from '@kbn/transform-plugin/common/api_sc import { catchAndWrapError } from '@kbn/security-solution-plugin/server/endpoint/utils'; import { installOrUpgradeEndpointFleetPackage } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/setup_fleet_for_endpoint'; import { EndpointError } from '@kbn/security-solution-plugin/common/endpoint/errors'; +import { STARTED_TRANSFORM_STATES } from '@kbn/security-solution-plugin/common/constants'; import { FtrService } from '../../functional/ftr_provider_context'; export class EndpointTestResources extends FtrService { @@ -89,11 +90,14 @@ export class EndpointTestResources extends FtrService { } private async startTransform(transformId: string) { - const transformsResponse = await this.esClient.transform.getTransform({ + const transformsResponse = await this.esClient.transform.getTransformStats({ transform_id: `${transformId}*`, }); return Promise.all( transformsResponse.transforms.map((transform) => { + if (STARTED_TRANSFORM_STATES.has(transform.state)) { + return Promise.resolve(); + } return this.esClient.transform.startTransform({ transform_id: transform.id }); }) ); @@ -137,6 +141,7 @@ export class EndpointTestResources extends FtrService { if (waitUntilTransformed) { // need this before indexing docs so that the united transform doesn't // create a checkpoint with a timestamp after the doc timestamps + await this.stopTransform(metadataTransformPrefix); await this.stopTransform(METADATA_UNITED_TRANSFORM); } @@ -158,6 +163,7 @@ export class EndpointTestResources extends FtrService { ); if (waitUntilTransformed) { + await this.startTransform(metadataTransformPrefix); const metadataIds = Array.from(new Set(indexedData.hosts.map((host) => host.agent.id))); await this.waitForEndpoints(metadataIds, waitTimeout); await this.startTransform(METADATA_UNITED_TRANSFORM); diff --git a/yarn.lock b/yarn.lock index 4297c9ea9ed74..57d384e8f4a02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1422,10 +1422,10 @@ version "0.0.0" uid "" -"@elastic/app-search-javascript@^7.13.1": - version "7.13.1" - resolved "https://registry.yarnpkg.com/@elastic/app-search-javascript/-/app-search-javascript-7.13.1.tgz#07d84daa27e856ad14f3f840683288eab06577f4" - integrity sha512-ShzZtGWykLQ0+wXzfk6lJztv68fRcGa8rsLDxJLH/O/2CGY+PJDnj8Qu5lJPmsAPZlZgaB8u7l26YGVPOoaqSA== +"@elastic/app-search-javascript@^8.1.2": + version "8.1.2" + resolved "https://registry.yarnpkg.com/@elastic/app-search-javascript/-/app-search-javascript-8.1.2.tgz#8b60d74deab05b6e8603078c013d75cbd41c2f80" + integrity sha512-o3S3GrwaKhZWedLDrPpyHwAhidovTqG0oEz0+f1Hk4mDLmnlViPBIJhg12KuCuM0MYFUavKDHoqARlsbW8z6Kw== dependencies: object-hash "^1.3.0" @@ -1595,22 +1595,23 @@ resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.5.1.tgz#96acf39c3d599950646ef8ccfd24a3f057cf4932" integrity sha512-Tby6TKjixRFY+atVNeYUdGr9m0iaOq8230KTwn8BbUhkh7LwozfgKq0U98HRX7n63ZL62szl+cDKTYzh5WPCFQ== -"@elastic/react-search-ui-views@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@elastic/react-search-ui-views/-/react-search-ui-views-1.6.0.tgz#7211d47c29ef0636c853721491b9905ac7ae58da" - integrity sha512-VADJ18p8HoSPtxKEWFODzId08j0ahyHmHjXv1vP6O/PvtA+ECvi0gDSh/WgdRF792G0e+4d2Dke8LIhxaEvE+w== +"@elastic/react-search-ui-views@1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@elastic/react-search-ui-views/-/react-search-ui-views-1.14.0.tgz#59e58f5c5f8769995baa0b5606da67e47535ea8b" + integrity sha512-zvW7Dzbf5thNTK1rTKrbsk8bCFK4d4YVtA0VjMSM0Y52CH6zVQrOqEsV/ilcIiHyvR7uFiiLHsFX6Yk3bHdrBw== dependencies: + "@elastic/search-ui" "1.14.0" downshift "^3.2.10" rc-pagination "^1.20.1" - react-select "^2.4.4" + react-select "^5.0.0" -"@elastic/react-search-ui@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@elastic/react-search-ui/-/react-search-ui-1.6.0.tgz#8d547d5e1f0a8eebe94798b29966f51643aa886f" - integrity sha512-bwSKuCQTQiBWr6QufQtZZGu6rcVYfoiUnyZbwZMS6ojedd5XY7FtMcE+QnR6/IIo0M2IUrxD74XtVNqkUhoCRg== +"@elastic/react-search-ui@^1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@elastic/react-search-ui/-/react-search-ui-1.14.0.tgz#cac7f6bba7cf84bf8c4e8d8deb14a443286c3dfc" + integrity sha512-vwOOpUjL/Y+o/53rt8VeBcjw7rPfg7eZMmSezjw3jYy17eC7mTIHTQf6knbyVO30R1KXl7LyAuyfQLS+ifX2zg== dependencies: - "@elastic/react-search-ui-views" "1.6.0" - "@elastic/search-ui" "1.6.0" + "@elastic/react-search-ui-views" "1.14.0" + "@elastic/search-ui" "1.14.0" "@elastic/request-crypto@2.0.1": version "2.0.1" @@ -1625,17 +1626,18 @@ version "0.0.0" uid "" -"@elastic/search-ui-app-search-connector@^1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@elastic/search-ui-app-search-connector/-/search-ui-app-search-connector-1.6.0.tgz#faf1c4a384285648ef7b5ef9cd0e65de0341d2b0" - integrity sha512-6oNvqzo4nuutmCM0zEzYrV6VwG8j0ML43SkaG6UrFzLUd6DeWUVGNN+SLNAlfQDWBUjc2m5EGvgdk/0GOWDZeA== +"@elastic/search-ui-app-search-connector@^1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@elastic/search-ui-app-search-connector/-/search-ui-app-search-connector-1.14.0.tgz#5ecd1157394b08f4281b890746450945413d582c" + integrity sha512-VwfbADt+PybgT+l2oK5gfW4WWHsf6al/5uk7qsYXtLneuckcjslUKzjlP5gHqAnV0/UKRXsYjjfb4WRDeo+iAw== dependencies: - "@elastic/app-search-javascript" "^7.13.1" + "@elastic/app-search-javascript" "^8.1.2" + "@elastic/search-ui" "1.14.0" -"@elastic/search-ui@1.6.0": - version "1.6.0" - resolved "https://registry.yarnpkg.com/@elastic/search-ui/-/search-ui-1.6.0.tgz#8b2286cacff44735be96605b2929ca9b469c78de" - integrity sha512-i7htjET9uE4xngyzS8kX3DkSD5XNcr+3FS0Jjx3xRpKVc/dFst4bJyiSeRrQcq+2oBb4mEJJOCFaIrLZg3mdSA== +"@elastic/search-ui@1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@elastic/search-ui/-/search-ui-1.14.0.tgz#e5bb2854ffa2571294769ee435d69bda56dc414b" + integrity sha512-6Q+KrE5AhHs20rvWATxv3LKe1u/6t8c/pRV0tJXfsxenFer6d33PSEqPm7uV/paTha+5K8JlayWapwsRRWrVGQ== dependencies: date-fns "^1.30.1" deep-equal "^1.0.1" @@ -1708,18 +1710,6 @@ "@emotion/babel-plugin" "^11.2.0" "@emotion/babel-plugin-jsx-pragmatic" "^0.1.5" -"@emotion/babel-utils@^0.6.4": - version "0.6.10" - resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc" - integrity sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow== - dependencies: - "@emotion/hash" "^0.6.6" - "@emotion/memoize" "^0.6.6" - "@emotion/serialize" "^0.9.1" - convert-source-map "^1.5.1" - find-root "^1.1.0" - source-map "^0.7.2" - "@emotion/cache@^10.0.27", "@emotion/cache@^10.0.9": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -1730,6 +1720,17 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" +"@emotion/cache@^11.4.0", "@emotion/cache@^11.9.3": + version "11.9.3" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.9.3.tgz#96638449f6929fd18062cfe04d79b29b44c0d6cb" + integrity sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.1.1" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "4.0.13" + "@emotion/cache@^11.7.1": version "11.7.1" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539" @@ -1786,11 +1787,6 @@ resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@emotion/hash@^0.6.2", "@emotion/hash@^0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.6.6.tgz#62266c5f0eac6941fece302abad69f2ee7e25e44" - integrity sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ== - "@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6", "@emotion/is-prop-valid@^0.8.8": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -1814,16 +1810,24 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== -"@emotion/memoize@^0.6.1", "@emotion/memoize@^0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b" - integrity sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ== - "@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== +"@emotion/react@^11.8.1": + version "11.9.3" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.3.tgz#f4f4f34444f6654a2e550f5dab4f2d360c101df9" + integrity sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/babel-plugin" "^11.7.1" + "@emotion/cache" "^11.9.3" + "@emotion/serialize" "^1.0.4" + "@emotion/utils" "^1.1.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + "@emotion/react@^11.9.0": version "11.9.0" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.0.tgz#b6d42b1db3bd7511e7a7c4151dc8bc82e14593b8" @@ -1848,16 +1852,6 @@ "@emotion/utils" "0.11.3" csstype "^2.5.7" -"@emotion/serialize@^0.9.1": - version "0.9.1" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.9.1.tgz#a494982a6920730dba6303eb018220a2b629c145" - integrity sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ== - dependencies: - "@emotion/hash" "^0.6.6" - "@emotion/memoize" "^0.6.6" - "@emotion/unitless" "^0.6.7" - "@emotion/utils" "^0.8.2" - "@emotion/serialize@^1.0.2", "@emotion/serialize@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.3.tgz#99e2060c26c6292469fb30db41f4690e1c8fea63" @@ -1869,6 +1863,17 @@ "@emotion/utils" "^1.0.0" csstype "^3.0.2" +"@emotion/serialize@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.4.tgz#ff31fd11bb07999611199c2229e152faadc21a3c" + integrity sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + "@emotion/sheet@0.9.4": version "0.9.4" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" @@ -1879,6 +1884,11 @@ resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2" integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g== +"@emotion/sheet@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.1.tgz#015756e2a9a3c7c5f11d8ec22966a8dbfbfac787" + integrity sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA== + "@emotion/styled-base@^10.0.27": version "10.0.31" resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" @@ -1902,31 +1912,16 @@ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== -"@emotion/stylis@^0.7.0": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5" - integrity sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ== - "@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== -"@emotion/unitless@^0.6.2", "@emotion/unitless@^0.6.7": - version "0.6.7" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.6.7.tgz#53e9f1892f725b194d5e6a1684a7b394df592397" - integrity sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg== - "@emotion/utils@0.11.3": version "0.11.3" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== -"@emotion/utils@^0.8.2": - version "0.8.2" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc" - integrity sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw== - "@emotion/utils@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" @@ -2949,6 +2944,10 @@ version "0.0.0" uid "" +"@kbn/aiops-components@link:bazel-bin/x-pack/packages/ml/aiops_components": + version "0.0.0" + uid "" + "@kbn/aiops-utils@link:bazel-bin/x-pack/packages/ml/aiops_utils": version "0.0.0" uid "" @@ -3589,6 +3588,14 @@ version "0.0.0" uid "" +"@kbn/type-summarizer-cli@link:bazel-bin/packages/kbn-type-summarizer-cli": + version "0.0.0" + uid "" + +"@kbn/type-summarizer-core@link:bazel-bin/packages/kbn-type-summarizer-core": + version "0.0.0" + uid "" + "@kbn/type-summarizer@link:bazel-bin/packages/kbn-type-summarizer": version "0.0.0" uid "" @@ -6001,6 +6008,13 @@ resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.7.tgz#34dc654d34fc058c41c31dbca1ed68071a8fcc17" integrity sha512-51vHWuUyDOi+8XuwPrTw3cFqyh2Slg9y8COYkRfjCPG9TfYqY0hoNPzv/8BrcAy0FeQBzqEo/D/8Nk2caOQJnA== +"@types/d3-brush@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.1.tgz#ae5f17ce391935ca88b29000e60ee20452c6357c" + integrity sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw== + dependencies: + "@types/d3-selection" "*" + "@types/d3-color@*": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-2.0.1.tgz#570ea7f8b853461301804efa52bd790a640a26db" @@ -6025,6 +6039,11 @@ dependencies: "@types/d3-time" "^1" +"@types/d3-selection@*", "@types/d3-selection@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.2.tgz#23e48a285b24063630bbe312cc0cfe2276de4a59" + integrity sha512-d29EDd0iUBrRoKhPndhDY6U/PYxOWqgIZwKTooy2UkBfU7TNZNpRho0yLWPxlatQrFWk2mnTu71IZQ4+LRgKlQ== + "@types/d3-shape@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.3.1.tgz#1b4f92b7efd7306fe2474dc6ee94c0f0ed2e6ab6" @@ -6042,6 +6061,13 @@ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.10.tgz#d338c7feac93a98a32aac875d1100f92c7b61f4f" integrity sha512-aKf62rRQafDQmSiv1NylKhIMmznsjRN+MnXRXTqHoqm0U/UZzVpdrtRnSIfdiLS616OuC1soYeX1dBg2n1u8Xw== +"@types/d3-transition@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.1.tgz#c9a96125567173d6163a6985b874f79154f4cc3d" + integrity sha512-Sv4qEI9uq3bnZwlOANvYK853zvpdKEm1yz9rcc8ZTsxvRklcs9Fx4YFuGA3gXoQN/c/1T6QkVNjhaRO/cWj94g== + dependencies: + "@types/d3-selection" "*" + "@types/d3@^3.5.43": version "3.5.43" resolved "https://registry.yarnpkg.com/@types/d3/-/d3-3.5.43.tgz#e9b4992817e0b6c5efaa7d6e5bb2cee4d73eab58" @@ -6473,6 +6499,10 @@ version "0.0.0" uid "" +"@types/kbn__aiops-components@link:bazel-bin/x-pack/packages/ml/aiops_components/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__aiops-utils@link:bazel-bin/x-pack/packages/ml/aiops_utils/npm_module_types": version "0.0.0" uid "" @@ -7077,6 +7107,18 @@ version "0.0.0" uid "" +"@types/kbn__type-summarizer-cli@link:bazel-bin/packages/kbn-type-summarizer-cli/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__type-summarizer-core@link:bazel-bin/packages/kbn-type-summarizer-core/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__type-summarizer@link:bazel-bin/packages/kbn-type-summarizer/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__typed-react-router-config@link:bazel-bin/packages/kbn-typed-react-router-config/npm_module_types": version "0.0.0" uid "" @@ -7601,6 +7643,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.0": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" + integrity sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA== + dependencies: + "@types/react" "*" + "@types/react-virtualized-auto-sizer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4" @@ -8711,11 +8760,6 @@ ansi-html@0.0.7: resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -8736,11 +8780,6 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= - ansi-styles@^2.0.1, ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -9038,6 +9077,11 @@ array-filter@^1.0.0: resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= +array-find-index@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + array-flat-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz#1e3a4255be619dfbffbfd1d635c1cf357cd034e7" @@ -9529,24 +9573,6 @@ babel-plugin-emotion@^10.0.27: find-root "^1.1.0" source-map "^0.5.7" -babel-plugin-emotion@^9.2.11: - version "9.2.11" - resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz#319c005a9ee1d15bb447f59fe504c35fd5807728" - integrity sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@emotion/babel-utils" "^0.6.4" - "@emotion/hash" "^0.6.2" - "@emotion/memoize" "^0.6.1" - "@emotion/stylis" "^0.7.0" - babel-plugin-macros "^2.0.0" - babel-plugin-syntax-jsx "^6.18.0" - convert-source-map "^1.5.0" - find-root "^1.1.0" - mkdirp "^0.5.1" - source-map "^0.5.7" - touch "^2.0.1" - babel-plugin-extract-import-names@1.6.22: version "1.6.22" resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" @@ -9749,10 +9775,10 @@ bach@^1.0.0: async-settle "^1.0.0" now-and-later "^2.0.0" -backport@^8.5.2: - version "8.5.2" - resolved "https://registry.yarnpkg.com/backport/-/backport-8.5.2.tgz#500f0f6b7a5bb78996c88553bfb7b6d85a3e627a" - integrity sha512-qtPWz51O02tnRN2qNMhWJvZNaNlh8K+LXNO8ktXiBIEAkrI96VVKzWNvOXcNYl8lOBq0ZYyPkAE6HH8W/l/tdA== +backport@^8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/backport/-/backport-8.8.0.tgz#eb01e1b4f2c713aad8ceae700ae71748e8f5cf8e" + integrity sha512-m+qr3ydtZADJLUDe3Gd8iFjhMS74IcuVH1QzGlZ7N2KPEC1j/zN+XEDCzofbOlA6T+dKPEagXEOFtBqLAgqkcA== dependencies: "@octokit/rest" "^18.12.0" axios "^0.27.2" @@ -10718,17 +10744,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - chance@1.0.18: version "1.0.18" resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.18.tgz#79788fe6fca4c338bf404321c347eecc80f969ee" @@ -11765,19 +11780,6 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-emotion@^9.2.12: - version "9.2.12" - resolved "https://registry.yarnpkg.com/create-emotion/-/create-emotion-9.2.12.tgz#0fc8e7f92c4f8bb924b0fef6781f66b1d07cb26f" - integrity sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA== - dependencies: - "@emotion/hash" "^0.6.2" - "@emotion/memoize" "^0.6.1" - "@emotion/stylis" "^0.7.0" - "@emotion/unitless" "^0.6.2" - csstype "^2.5.2" - stylis "^3.5.0" - stylis-rule-sheet "^0.0.10" - create-hash@^1.1.0, create-hash@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" @@ -12162,11 +12164,6 @@ csstype@^2.2.0, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ== -csstype@^2.5.2: - version "2.6.14" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.14.tgz#004822a4050345b55ad4dcc00be1d9cf2f4296de" - integrity sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A== - csstype@^3.0.2: version "3.0.7" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" @@ -12306,6 +12303,17 @@ d3-array@2, d3-array@^2.3.0: dependencies: internmap "^1.0.0" +d3-brush@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" + integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "3" + d3-transition "3" + d3-cloud@1.2.5, d3-cloud@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/d3-cloud/-/d3-cloud-1.2.5.tgz#3e91564f2d27fba47fcc7d812eb5081ea24c603d" @@ -12352,6 +12360,14 @@ d3-delaunay@^6.0.2: resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA== +"d3-drag@2 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== + dependencies: + d3-dispatch "1 - 3" + d3-selection "3" + d3-dsv@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c" @@ -12370,6 +12386,11 @@ d3-dsv@^3.0.1: iconv-lite "0.6" rw "1" +"d3-ease@1 - 3": + version "1.0.6" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.6.tgz#ebdb6da22dfac0a22222f2d4da06f66c416a0ec0" + integrity sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ== + d3-force@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" @@ -12434,6 +12455,13 @@ d3-interpolate@1, d3-interpolate@^1.1.4: dependencies: d3-color "1" +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + "d3-interpolate@1.2.0 - 2": version "2.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" @@ -12441,13 +12469,6 @@ d3-interpolate@1, d3-interpolate@^1.1.4: dependencies: d3-color "1 - 2" -"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" - integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== - dependencies: - d3-color "1 - 3" - d3-path@1: version "1.0.9" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" @@ -12524,6 +12545,11 @@ d3-scale@^4.0.2: d3-time "2.1.1 - 3" d3-time-format "2 - 4" +d3-selection@3, d3-selection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== + d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.3.7" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" @@ -12600,6 +12626,17 @@ d3-timer@^3.0.1: resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== +d3-transition@3, d3-transition@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" + d3-voronoi@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" @@ -13319,13 +13356,6 @@ dom-converter@~0.2: dependencies: utila "~0.4" -dom-helpers@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" - integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== - dependencies: - "@babel/runtime" "^7.1.2" - dom-helpers@^5.0.0, dom-helpers@^5.0.1: version "5.1.4" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.4.tgz#4609680ab5c79a45f2531441f1949b79d6587f4b" @@ -13742,14 +13772,6 @@ emotion-theming@^10.0.27: "@emotion/weak-memoize" "0.2.5" hoist-non-react-statics "^3.3.0" -emotion@^9.1.2: - version "9.2.12" - resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.12.tgz#53925aaa005614e65c6e43db8243c843574d1ea9" - integrity sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ== - dependencies: - babel-plugin-emotion "^9.2.11" - create-emotion "^9.2.12" - enabled@2.0.x: version "2.0.0" resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" @@ -14110,7 +14132,7 @@ escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -14155,10 +14177,10 @@ escodegen@~1.2.0: optionalDependencies: source-map "~0.1.30" -eslint-config-prettier@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz#f4a4bd2832e810e8cc7c1411ec85b3e85c0c53f9" - integrity sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg== +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== eslint-formatter-pretty@^4.1.0: version "4.1.0" @@ -16436,13 +16458,6 @@ hard-rejection@^2.1.0: resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= - dependencies: - ansi-regex "^0.2.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -19622,21 +19637,21 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -license-checker@^16.0.0: - version "16.0.0" - resolved "https://registry.yarnpkg.com/license-checker/-/license-checker-16.0.0.tgz#4e1263e5314b4519f8c3a5fe2fe4f38bc645a87d" - integrity sha512-TAZDfuhEM1oZcBXICOeTBMt+bXIHllvoKHZA658YgPLzcnT45MS2Tjqqwkd5ctkHOlKJ8fTdl5dft2YTCe/4LQ== +license-checker@^25.0.1: + version "25.0.1" + resolved "https://registry.yarnpkg.com/license-checker/-/license-checker-25.0.1.tgz#4d14504478a5240a857bb3c21cd0491a00d761fa" + integrity sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g== dependencies: - chalk "~0.5.1" - debug "^2.2.0" - mkdirp "^0.3.5" - nopt "^2.2.0" + chalk "^2.4.1" + debug "^3.1.0" + mkdirp "^0.5.1" + nopt "^4.0.1" read-installed "~4.0.3" - semver "^5.3.0" - spdx "^0.5.1" - spdx-correct "^2.0.3" - spdx-satisfies "^0.1.3" - treeify "^1.0.1" + semver "^5.5.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + spdx-satisfies "^4.0.0" + treeify "^1.1.0" lie@~3.3.0: version "3.3.0" @@ -20925,11 +20940,6 @@ mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= - mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" @@ -21067,10 +21077,10 @@ moment-timezone@^0.5.27: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.29.2: - version "2.29.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" - integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== +"moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== monaco-editor@*, monaco-editor@^0.22.3: version "0.22.3" @@ -21601,12 +21611,13 @@ nodemon@^2.0.4: undefsafe "^2.0.5" update-notifier "^5.1.0" -nopt@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-2.2.1.tgz#2aa09b7d1768487b3b89a9c5aa52335bff0baea7" - integrity sha1-KqCbfRdoSHs7ianFqlIzW/8Lrqc= +nopt@^4.0.1, nopt@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== dependencies: abbrev "1" + osenv "^0.1.4" nopt@^5.0.0: version "5.0.0" @@ -21622,14 +21633,6 @@ nopt@~1.0.10: dependencies: abbrev "1" -nopt@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" - integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== - dependencies: - abbrev "1" - osenv "^0.1.4" - normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -23550,10 +23553,10 @@ prettier-linter-helpers@^1.0.0: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== -prettier@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" - integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== pretty-bytes@^5.6.0: version "5.6.0" @@ -24065,7 +24068,7 @@ raf-schd@^4.0.0, raf-schd@^4.0.2: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== -raf@^3.1.0, raf@^3.4.0, raf@^3.4.1: +raf@^3.1.0, raf@^3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -24387,13 +24390,6 @@ react-hook-form@^7.30.0: resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.30.0.tgz#c9e2fd54d3627e43bd94bf38ef549df2e80c1371" integrity sha512-DzjiM6o2vtDGNMB9I4yCqW8J21P314SboNG1O0obROkbg7KVS0I7bMtwSdKyapnCPjHgnxc3L7E5PEdISeEUcQ== -react-input-autosize@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.2.tgz#fcaa7020568ec206bc04be36f4eb68e647c4d8c2" - integrity sha512-jQJgYCA3S0j+cuOwzuCd1OjmBmnZLdqQdiLKRYrsMMzbjUrVDS5RvJUDwJqA7sKuksDuzFtm6hZGKFu7Mjk5aw== - dependencies: - prop-types "^15.5.8" - react-input-autosize@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85" @@ -24655,19 +24651,6 @@ react-router@6.3.0, react-router@^6.0.0: dependencies: history "^5.2.0" -react-select@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.4.4.tgz#ba72468ef1060c7d46fbb862b0748f96491f1f73" - integrity sha512-C4QPLgy9h42J/KkdrpVxNmkY6p4lb49fsrbDk/hRcZpX7JvZPNb6mGj+c5SzyEtBv1DmQ9oPH4NmhAFvCrg8Jw== - dependencies: - classnames "^2.2.5" - emotion "^9.1.2" - memoize-one "^5.0.0" - prop-types "^15.6.0" - raf "^3.4.0" - react-input-autosize "^2.2.1" - react-transition-group "^2.2.1" - react-select@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.2.0.tgz#de9284700196f5f9b5277c5d850a9ce85f5c72fe" @@ -24682,6 +24665,19 @@ react-select@^3.2.0: react-input-autosize "^3.0.0" react-transition-group "^4.3.0" +react-select@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.3.2.tgz#ecee0d5c59ed4acb7f567f7de3c75a488d93dacb" + integrity sha512-W6Irh7U6Ha7p5uQQ2ZnemoCQ8mcfgOtHfw3wuMzG6FAu0P+CYicgofSLOq97BhjMx8jS+h+wwWdCBeVVZ9VqlQ== + dependencies: + "@babel/runtime" "^7.12.0" + "@emotion/cache" "^11.4.0" + "@emotion/react" "^11.8.1" + "@types/react-transition-group" "^4.4.0" + memoize-one "^5.0.0" + prop-types "^15.6.0" + react-transition-group "^4.3.0" + react-shortcuts@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/react-shortcuts/-/react-shortcuts-2.0.1.tgz#6de302244ce33ac9f9273dbeec540a8f81808f91" @@ -24768,16 +24764,6 @@ react-tiny-virtual-list@^2.2.0: dependencies: prop-types "^15.5.7" -react-transition-group@^2.2.1: - version "2.9.0" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" - integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== - dependencies: - dom-helpers "^3.4.0" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react-lifecycles-compat "^3.0.4" - react-transition-group@^4.3.0: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" @@ -26229,7 +26215,7 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -26826,7 +26812,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.2, source-map@^0.7.3: +source-map@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -26880,21 +26866,22 @@ spawn-wrap@^2.0.0: signal-exit "^3.0.2" which "^2.0.1" -spdx-compare@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/spdx-compare/-/spdx-compare-0.1.2.tgz#b06af3ea34af7437d91a9f449eaf2d2e93c3c8fb" - integrity sha1-sGrz6jSvdDfZGp9Enq8tLpPDyPs= +spdx-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/spdx-compare/-/spdx-compare-1.0.0.tgz#2c55f117362078d7409e6d7b08ce70a857cd3ed7" + integrity sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A== dependencies: - spdx-expression-parse "^1.0.0" - spdx-ranges "^1.0.0" + array-find-index "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-ranges "^2.0.0" -spdx-correct@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-2.0.4.tgz#d1652ad2ebc516f656f66ea93398558065f1b4a4" - integrity sha512-c+4gPpt9YDhz7cHlz5UrsHzxxRi4ksclxnEEKsuGT9JdwSC+ZNmsGbYRzzgxyZaBYpcWnlu+4lPcdLKx4DOCmA== +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== dependencies: - spdx-expression-parse "^2.0.1" - spdx-license-ids "^2.0.1" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" spdx-correct@~1.0.0: version "1.0.2" @@ -26903,59 +26890,47 @@ spdx-correct@~1.0.0: dependencies: spdx-license-ids "^1.0.2" -spdx-exceptions@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-1.0.5.tgz#9d21ac4da4bdb71d060fb74e5a67531d032cbba6" - integrity sha1-nSGsTaS9tx0GD7dOWmdTHQMsu6Y= +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== -spdx-exceptions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" - integrity sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg== +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@^1.0.0, spdx-expression-parse@~1.0.0: +spdx-expression-parse@~1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" integrity sha1-m98vIOH0DtRH++JzJmGR/O1RYmw= -spdx-expression-parse@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-2.0.2.tgz#e2e0f229c057eac704c5a6d1c687eed66aca034b" - integrity sha512-oFxOkWCfFS0ltNp0H66gXlU4NF6bxg7RkoTYR0413t+yTY9zyj+AIWsjtN8dcVp6703ijDYBWBIARlJ7DkyP9Q== - dependencies: - spdx-exceptions "^2.0.0" - spdx-license-ids "^2.0.1" - -spdx-license-ids@^1.0.0, spdx-license-ids@^1.0.2: +spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" integrity sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc= -spdx-license-ids@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-2.0.1.tgz#02017bcc3534ee4ffef6d58d20e7d3e9a1c3c8ec" - integrity sha1-AgF7zDU07k/+9tWNIOfT6aHDyOw= - -spdx-ranges@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/spdx-ranges/-/spdx-ranges-1.0.1.tgz#0f4eec7b8ea48ed202e374bb8942e8d18dc0113e" - integrity sha1-D07se46kjtIC43S7iULo0Y3AET4= +spdx-license-ids@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== -spdx-satisfies@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/spdx-satisfies/-/spdx-satisfies-0.1.3.tgz#67a1f274e6115d4aae28afe474db76164be10bdc" - integrity sha1-Z6HydOYRXUquKK/kdNt2FkvhC9w= - dependencies: - spdx-compare "^0.1.2" - spdx-expression-parse "^1.0.0" +spdx-ranges@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/spdx-ranges/-/spdx-ranges-2.1.1.tgz#87573927ba51e92b3f4550ab60bfc83dd07bac20" + integrity sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA== -spdx@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/spdx/-/spdx-0.5.1.tgz#d36c275088b48d75a9046cd44a838ce4b5339998" - integrity sha1-02wnUIi0jXWpBGzUSoOM5LUzmZg= +spdx-satisfies@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz#9a09a68d80f5f1a31cfaebb384b0c6009e4969fe" + integrity sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA== dependencies: - spdx-exceptions "^1.0.0" - spdx-license-ids "^1.0.0" + spdx-compare "^1.0.0" + spdx-expression-parse "^3.0.0" + spdx-ranges "^2.0.0" spdy-transport@^3.0.0: version "3.0.0" @@ -27451,13 +27426,6 @@ strip-ansi@*, strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi dependencies: ansi-regex "^4.1.0" -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= - dependencies: - ansi-regex "^0.2.1" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -27654,11 +27622,6 @@ stylelint@13.8.0: v8-compile-cache "^2.2.0" write-file-atomic "^3.0.3" -stylis-rule-sheet@^0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" - integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== - stylis@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" @@ -27669,11 +27632,6 @@ stylis@4.0.13: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== -stylis@^3.5.0: - version "3.5.4" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" - integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== - subarg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" @@ -27747,11 +27705,6 @@ supports-color@8.1.1, supports-color@^8.1.1: dependencies: has-flag "^4.0.0" -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -28368,13 +28321,6 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== -touch@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/touch/-/touch-2.0.2.tgz#ca0b2a3ae3211246a61b16ba9e6cbf1596287164" - integrity sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A== - dependencies: - nopt "~1.0.10" - touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -28437,7 +28383,7 @@ tree-kill@^1.2.2: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== -treeify@^1.0.1, treeify@^1.1.0: +treeify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==