diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 721de7917534d..0b56007cf9aeb 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -128,7 +128,12 @@ enabled: - x-pack/test/alerting_api_integration/security_and_spaces/group2/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group3/config.ts - x-pack/test/alerting_api_integration/security_and_spaces/group2/config_non_dedicated_task_runner.ts - - x-pack/test/alerting_api_integration/spaces_only/config.ts + - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.ts + - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts + - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/config.ts + - x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/config.ts + - x-pack/test/alerting_api_integration/spaces_only/tests/actions/config.ts + - x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/config.ts - x-pack/test/api_integration_basic/config.ts - x-pack/test/api_integration/config_security_basic.ts - x-pack/test/api_integration/config_security_trial.ts @@ -161,7 +166,16 @@ enabled: - x-pack/test/fleet_api_integration/config.ts - x-pack/test/fleet_functional/config.ts - x-pack/test/ftr_apis/security_and_spaces/config.ts - - x-pack/test/functional_basic/config.ts + - x-pack/test/functional_basic/apps/ml/permissions/config.ts + - x-pack/test/functional_basic/apps/ml/data_visualizer/group1/config.ts + - x-pack/test/functional_basic/apps/ml/data_visualizer/group2/config.ts + - x-pack/test/functional_basic/apps/ml/data_visualizer/group3/config.ts + - x-pack/test/functional_basic/apps/transform/creation/index_pattern/config.ts + - x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts + - x-pack/test/functional_basic/apps/transform/edit_clone/config.ts + - x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/config.ts + - x-pack/test/functional_basic/apps/transform/permissions/config.ts + - x-pack/test/functional_basic/apps/transform/feature_controls/config.ts - x-pack/test/functional_cors/config.ts - x-pack/test/functional_embedded/config.ts - x-pack/test/functional_enterprise_search/without_host_configured.config.ts @@ -216,7 +230,12 @@ enabled: - x-pack/test/functional/apps/snapshot_restore/config.ts - x-pack/test/functional/apps/spaces/config.ts - x-pack/test/functional/apps/status_page/config.ts - - x-pack/test/functional/apps/transform/config.ts + - x-pack/test/functional/apps/transform/creation/index_pattern/config.ts + - x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts + - x-pack/test/functional/apps/transform/start_reset_delete/config.ts + - x-pack/test/functional/apps/transform/edit_clone/config.ts + - x-pack/test/functional/apps/transform/permissions/config.ts + - x-pack/test/functional/apps/transform/feature_controls/config.ts - x-pack/test/functional/apps/upgrade_assistant/config.ts - x-pack/test/functional/apps/uptime/config.ts - x-pack/test/functional/apps/visualize/config.ts diff --git a/.buildkite/scripts/steps/test/kbn_handlebars.sh b/.buildkite/scripts/steps/test/kbn_handlebars.sh index 0e7fc6ebb0648..b66bc75679211 100755 --- a/.buildkite/scripts/steps/test/kbn_handlebars.sh +++ b/.buildkite/scripts/steps/test/kbn_handlebars.sh @@ -4,5 +4,5 @@ set -euo pipefail source .buildkite/scripts/common/util.sh -echo '--- Checking for @kbn/handlebars test changes' -packages/kbn-handlebars/scripts/check_for_test_changes.sh +echo '--- Checking for @kbn/handlebars upstream updates' +packages/kbn-handlebars/scripts/check_for_upstream_updates.sh diff --git a/.eslintrc.js b/.eslintrc.js index 8fd3029a0592c..d79b0400368ab 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -518,7 +518,7 @@ module.exports = { }, }, { - files: ['packages/kbn-handlebars/src/upstream/**/*.{js,mjs,ts,tsx}'], + files: ['packages/kbn-handlebars/src/spec/**/*.{js,mjs,ts,tsx}'], rules: { '@kbn/eslint/require-license-header': [ 'error', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 59455a100f427..524d2abbc7f9e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1040,9 +1040,9 @@ packages/shared-ux/button/exit_full_screen/types @elastic/appex-sharedux packages/shared-ux/card/no_data/impl @elastic/appex-sharedux packages/shared-ux/card/no_data/mocks @elastic/appex-sharedux packages/shared-ux/card/no_data/types @elastic/appex-sharedux -packages/shared-ux/code_editor/impl @elastic/shared-ux -packages/shared-ux/code_editor/mocks @elastic/shared-ux -packages/shared-ux/code_editor/types @elastic/shared-ux +packages/shared-ux/code_editor/impl @elastic/appex-sharedux +packages/shared-ux/code_editor/mocks @elastic/appex-sharedux +packages/shared-ux/code_editor/types @elastic/appex-sharedux packages/shared-ux/file/context @elastic/appex-sharedux packages/shared-ux/file/file_picker/impl @elastic/appex-sharedux packages/shared-ux/file/file_upload/impl @elastic/appex-sharedux diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 806c6cbf07a0a..f221a780c7422 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,14 +17,13 @@ jobs: fail-fast: false matrix: language: [ 'javascript' ] - # branch: [ 'main', '7.17' ] + branch: [ 'main', '7.17' ] steps: - name: Checkout repository uses: actions/checkout@v3 - # TODO: Enable once a `.github/codeql/codeql-config.yml` file has been committed to 7.17 - # with: - # ref: ${{ matrix.branch }} + with: + ref: ${{ matrix.branch }} - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index a36bd508f0ad0..ef292b2861922 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 6855f8f9002c8..df559d1d23dfd 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index 2afb1f1ffe062..d1154365f6269 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index ed0c19af12faf..6c7bfae415297 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.devdocs.json b/api_docs/apm.devdocs.json index ec829fca26741..95bf2cd443bc6 100644 --- a/api_docs/apm.devdocs.json +++ b/api_docs/apm.devdocs.json @@ -202,7 +202,7 @@ "APMPluginSetupDependencies", ") => { config$: ", "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; forceSyntheticSource: boolean; }>>; getApmIndices: () => Promise>; createApmEventClient: ({ request, context, debug, }: { debug?: boolean | undefined; request: ", { @@ -448,7 +448,7 @@ "label": "config", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", + "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; readonly forceSyntheticSource: boolean; }" ], @@ -840,7 +840,7 @@ "label": "APMConfig", "description": [], "signature": [ - "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", + "{ readonly indices: Readonly<{} & { metric: string; error: string; span: string; transaction: string; onboarding: string; }>; readonly autoCreateApmDataView: boolean; readonly serviceMapEnabled: boolean; readonly serviceMapFingerprintBucketSize: number; readonly serviceMapFingerprintGlobalBucketSize: number; readonly serviceMapTraceIdBucketSize: number; readonly serviceMapTraceIdGlobalBucketSize: number; readonly serviceMapMaxTracesPerRequest: number; readonly ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; readonly searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; readonly telemetryCollectionEnabled: boolean; readonly metricsInterval: number; readonly agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; readonly forceSyntheticSource: boolean; }" ], @@ -4798,7 +4798,7 @@ "section": "def-server.APMRouteHandlerResources", "text": "APMRouteHandlerResources" }, - ", { transactionGroups: { transactionType: string; name: string; latency: number | null; throughput: number; errorRate: number; impact: number; }[]; isAggregationAccurate: boolean; bucketSize: number; }, ", + ", { transactionGroups: { transactionType: string; name: string; latency: number | null; throughput: number; errorRate: number; impact: number; }[]; transactionOverflowCount: number; maxTransactionGroupsExceeded: boolean; }, ", "APMRouteCreateOptions", ">; \"GET /internal/apm/traces/{traceId}/spans/{spanId}\": ", { @@ -6396,7 +6396,7 @@ "AgentName", "; } & { serviceName: string; healthStatus: ", "ServiceHealthStatus", - "; } & { serviceName: string; alertsCount: number; }>; }, ", + "; } & { serviceName: string; alertsCount: number; }>; maxServiceCountExceeded: boolean; serviceOverflowCount: number; }, ", "APMRouteCreateOptions", ">; \"GET /internal/apm/service-map/dependency\": ", { @@ -7679,7 +7679,7 @@ "description": [], "signature": [ "Observable", - "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; transactionGroupBucketSize: number; maxTraceItems: number; }>; searchAggregatedTransactions: ", + "; autoCreateApmDataView: boolean; serviceMapEnabled: boolean; serviceMapFingerprintBucketSize: number; serviceMapFingerprintGlobalBucketSize: number; serviceMapTraceIdBucketSize: number; serviceMapTraceIdGlobalBucketSize: number; serviceMapMaxTracesPerRequest: number; ui: Readonly<{} & { enabled: boolean; maxTraceItems: number; }>; searchAggregatedTransactions: ", "SearchAggregatedTransactionSetting", "; telemetryCollectionEnabled: boolean; metricsInterval: number; agent: Readonly<{} & { migrations: Readonly<{} & { enabled: boolean; }>; }>; forceSyntheticSource: boolean; }>>" ], diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index a0a63f1dd4367..c34d16be46cf6 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index bb08afc588830..a3b03ad4a4452 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index eae171c59adc6..908a8ec253f33 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 0d228f8ad9a4d..f771a12f1eba2 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 227d548020973..a9357e0c71e8b 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 7743510f26d88..048018c70181e 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index fe1dec5fe6310..45aa0fa519fd9 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_chat.mdx b/api_docs/cloud_chat.mdx index ce2e358ea9f50..84bef08b70fa7 100644 --- a/api_docs/cloud_chat.mdx +++ b/api_docs/cloud_chat.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudChat title: "cloudChat" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudChat plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudChat'] --- import cloudChatObj from './cloud_chat.devdocs.json'; diff --git a/api_docs/cloud_data_migration.mdx b/api_docs/cloud_data_migration.mdx index a90893f737fb1..23d0f69cd9546 100644 --- a/api_docs/cloud_data_migration.mdx +++ b/api_docs/cloud_data_migration.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDataMigration title: "cloudDataMigration" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDataMigration plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDataMigration'] --- import cloudDataMigrationObj from './cloud_data_migration.devdocs.json'; diff --git a/api_docs/cloud_defend.mdx b/api_docs/cloud_defend.mdx index 0f4a756b7d724..ab988904fc49b 100644 --- a/api_docs/cloud_defend.mdx +++ b/api_docs/cloud_defend.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudDefend title: "cloudDefend" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudDefend plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudDefend'] --- import cloudDefendObj from './cloud_defend.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index d82d0233f9c1b..a1f8d009a7ab5 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index ce8800ed5cb1c..298bc5370283d 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 35c90d18566f3..437b9eb6c4613 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/content_management.devdocs.json b/api_docs/content_management.devdocs.json new file mode 100644 index 0000000000000..338c6568fdb4d --- /dev/null +++ b/api_docs/content_management.devdocs.json @@ -0,0 +1,100 @@ +{ + "id": "contentManagement", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "start": { + "parentPluginId": "contentManagement", + "id": "def-public.ContentManagementPublicStart", + "type": "Interface", + "tags": [], + "label": "ContentManagementPublicStart", + "description": [], + "path": "src/plugins/content_management/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [], + "setup": { + "parentPluginId": "contentManagement", + "id": "def-server.ContentManagementServerSetup", + "type": "Interface", + "tags": [], + "label": "ContentManagementServerSetup", + "description": [], + "path": "src/plugins/content_management/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "setup", + "initialIsOpen": true + }, + "start": { + "parentPluginId": "contentManagement", + "id": "def-server.ContentManagementServerStart", + "type": "Interface", + "tags": [], + "label": "ContentManagementServerStart", + "description": [], + "path": "src/plugins/content_management/server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "lifecycle": "start", + "initialIsOpen": true + } + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "contentManagement", + "id": "def-common.API_ENDPOINT", + "type": "string", + "tags": [], + "label": "API_ENDPOINT", + "description": [], + "signature": [ + "\"/api/content_management\"" + ], + "path": "src/plugins/content_management/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "contentManagement", + "id": "def-common.PLUGIN_ID", + "type": "string", + "tags": [], + "label": "PLUGIN_ID", + "description": [], + "signature": [ + "\"contentManagement\"" + ], + "path": "src/plugins/content_management/common/constants.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/content_management.mdx b/api_docs/content_management.mdx new file mode 100644 index 0000000000000..1964343a7d3eb --- /dev/null +++ b/api_docs/content_management.mdx @@ -0,0 +1,43 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibContentManagementPluginApi +slug: /kibana-dev-docs/api/contentManagement +title: "contentManagement" +image: https://source.unsplash.com/400x175/?github +description: API docs for the contentManagement plugin +date: 2023-02-02 +tags: ['contributor', 'dev', 'apidocs', 'kibana', 'contentManagement'] +--- +import contentManagementObj from './content_management.devdocs.json'; + +Content management app + +Contact [@elastic/kibana-global-experience](https://github.com/orgs/elastic/teams/@elastic/kibana-global-experience) for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 5 | 0 | 5 | 0 | + +## Client + +### Start + + +## Server + +### Setup + + +### Start + + +## Common + +### Consts, variables and types + + diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index 266c100addc1c..19ce784eea455 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index 04ff270cbf464..d254c4480e9a8 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -5098,27 +5098,27 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" }, { "plugin": "synthetics", @@ -5408,6 +5408,110 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-public.CustomBrandingSetup", + "type": "Interface", + "tags": [], + "label": "CustomBrandingSetup", + "description": [], + "path": "packages/core/custom-branding/core-custom-branding-browser/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-public.CustomBrandingSetup.customBranding$", + "type": "Object", + "tags": [], + "label": "customBranding$", + "description": [], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/core-custom-branding-common", + "scope": "common", + "docId": "kibKbnCoreCustomBrandingCommonPluginApi", + "section": "def-common.CustomBranding", + "text": "CustomBranding" + }, + ">" + ], + "path": "packages/core/custom-branding/core-custom-branding-browser/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-public.CustomBrandingSetup.hasCustomBranding$", + "type": "Object", + "tags": [], + "label": "hasCustomBranding$", + "description": [], + "signature": [ + "Observable", + "" + ], + "path": "packages/core/custom-branding/core-custom-branding-browser/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "core", + "id": "def-public.CustomBrandingStart", + "type": "Interface", + "tags": [], + "label": "CustomBrandingStart", + "description": [], + "path": "packages/core/custom-branding/core-custom-branding-browser/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-public.CustomBrandingStart.customBranding$", + "type": "Object", + "tags": [], + "label": "customBranding$", + "description": [], + "signature": [ + "Observable", + "<", + { + "pluginId": "@kbn/core-custom-branding-common", + "scope": "common", + "docId": "kibKbnCoreCustomBrandingCommonPluginApi", + "section": "def-common.CustomBranding", + "text": "CustomBranding" + }, + ">" + ], + "path": "packages/core/custom-branding/core-custom-branding-browser/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "core", + "id": "def-public.CustomBrandingStart.hasCustomBranding$", + "type": "Object", + "tags": [], + "label": "hasCustomBranding$", + "description": [], + "signature": [ + "Observable", + "" + ], + "path": "packages/core/custom-branding/core-custom-branding-browser/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-public.DeprecationsServiceStart", @@ -15033,15 +15137,15 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" }, { "plugin": "graph", @@ -15061,11 +15165,11 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts" }, { "plugin": "savedObjects", @@ -21018,6 +21122,14 @@ "plugin": "cases", "path": "x-pack/plugins/cases/server/services/so_references.ts" }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/server/services/so_references.ts" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/server/services/so_references.ts" + }, { "plugin": "lists", "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts" @@ -22727,6 +22839,18 @@ "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/share_action.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/common/visualizations/lens/types.ts" @@ -22798,6 +22922,14 @@ { "plugin": "cases", "path": "x-pack/plugins/cases/server/services/user_actions/test_utils.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.test.ts" } ], "initialIsOpen": false @@ -32746,6 +32878,148 @@ ], "initialIsOpen": false }, + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup", + "type": "Interface", + "tags": [], + "label": "CustomBrandingSetup", + "description": [], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup.register", + "type": "Function", + "tags": [], + "label": "register", + "description": [], + "signature": [ + "(fetchFn: ", + { + "pluginId": "@kbn/core-custom-branding-server", + "scope": "common", + "docId": "kibKbnCoreCustomBrandingServerPluginApi", + "section": "def-common.CustomBrandingFetchFn", + "text": "CustomBrandingFetchFn" + }, + ") => void" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup.register.$1", + "type": "Function", + "tags": [], + "label": "fetchFn", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-custom-branding-server", + "scope": "common", + "docId": "kibKbnCoreCustomBrandingServerPluginApi", + "section": "def-common.CustomBrandingFetchFn", + "text": "CustomBrandingFetchFn" + } + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [] + }, + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup.getBrandingFor", + "type": "Function", + "tags": [], + "label": "getBrandingFor", + "description": [], + "signature": [ + "(request: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + ", options: { unauthenticated?: boolean | undefined; }) => Promise<", + { + "pluginId": "@kbn/core-custom-branding-common", + "scope": "common", + "docId": "kibKbnCoreCustomBrandingCommonPluginApi", + "section": "def-common.CustomBranding", + "text": "CustomBranding" + }, + ">" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup.getBrandingFor.$1", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup.getBrandingFor.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "core", + "id": "def-server.CustomBrandingSetup.getBrandingFor.$2.unauthenticated", + "type": "CompoundType", + "tags": [], + "label": "unauthenticated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] + } + ], + "initialIsOpen": false + }, { "parentPluginId": "core", "id": "def-server.CustomHttpResponseOptions", diff --git a/api_docs/core.mdx b/api_docs/core.mdx index da92cad1c112c..8cf73d1db3450 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2832 | 17 | 1016 | 0 | +| 2845 | 17 | 1029 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index 32e364fe911fb..2cca32c7fb2e3 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 800cf884e2595..d49a3f1385889 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index cc5ff14a7dff3..8c8272de31cb6 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.devdocs.json b/api_docs/data.devdocs.json index b8f5b5f037f20..762d74c33d14e 100644 --- a/api_docs/data.devdocs.json +++ b/api_docs/data.devdocs.json @@ -3540,7 +3540,7 @@ "label": "getActiveIndexFilter", "description": [], "signature": [ - "() => string[]" + "() => any[]" ], "path": "src/plugins/data/common/search/search_source/search_source.ts", "deprecated": false, @@ -10174,7 +10174,7 @@ "section": "def-common.SearchSourceFields", "text": "SearchSourceFields" }, - "[K]; getActiveIndexFilter: () => string[]; getOwnField: any[]; getOwnField: string[]; getOwnField: any[]; getOwnField: string[]" + "() => any[]" ], "path": "src/plugins/data/common/search/search_source/search_source.ts", "deprecated": false, @@ -35050,7 +35050,7 @@ "section": "def-common.SearchSourceFields", "text": "SearchSourceFields" }, - "[K]; getActiveIndexFilter: () => string[]; getOwnField: any[]; getOwnField: | discover, monitoring | - | | | @kbn/core-saved-objects-common, @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-api-server, core, home, dataViews, discover, savedObjectsTagging, savedObjectsFinder, fleet, canvas, osquery, securitySolution, synthetics, savedObjects, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-server, @kbn/core-saved-objects-import-export-server-internal, apm, savedObjectsTaggingOss, savedObjectsManagement, cases, lists, upgradeAssistant, @kbn/core-ui-settings-server-internal, data, dashboard | - | | | @kbn/core-saved-objects-common, @kbn/core-saved-objects-api-browser, @kbn/core-saved-objects-browser-internal, @kbn/core-saved-objects-api-server, core, home, dataViews, discover, savedObjectsTagging, savedObjectsFinder, fleet, canvas, osquery, securitySolution, synthetics, savedObjects, @kbn/core-saved-objects-browser-mocks, @kbn/core-saved-objects-server, @kbn/core-saved-objects-import-export-server-internal, apm, savedObjectsTaggingOss, savedObjectsManagement, cases, lists, upgradeAssistant, @kbn/core-ui-settings-server-internal, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, discover, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, data | - | | | discover | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover, data | - | -| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, discover | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, dataViews, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, discover, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega, data | - | +| | @kbn/es-query, securitySolution, timelines, lists, threatIntelligence, data, unifiedSearch, triggersActionsUi, savedObjectsManagement, controls, unifiedFieldList, lens, discover, aiops, ml, infra, visTypeTimeseries, apm, observability, dataVisualizer, fleet, canvas, graph, stackAlerts, synthetics, transform, upgradeAssistant, ux, maps, dataViewManagement, inputControlVis, visDefaultEditor, presentationUtil, visTypeTimelion, visTypeVega | - | | | data, discover, imageEmbeddable, embeddable | - | | | advancedSettings, discover | - | | | @kbn/core-plugins-browser-internal, @kbn/core-root-browser-internal, dataViews, home, data, savedObjects, embeddable, unifiedSearch, presentationUtil, visualizations, dashboard, lens, discover, cases, fileUpload, maps, ml, infra, fleet, canvas, dashboardEnhanced, graph, synthetics, transform, watcher, dataVisualizer, cloudSecurityPosture, securitySolution | - | diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index fd4dae4f0f318..65180c999c8eb 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -478,8 +478,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Deprecated API | Reference location(s) | Remove By | | ---------------|-----------|-----------| -| | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 22 more | - | -| | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 38 more | - | +| | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 26 more | - | +| | [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [so_references.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/so_references.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [find.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/operations/find.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject), [test_utils.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/server/services/user_actions/test_utils.ts#:~:text=SavedObject)+ 44 more | - | | | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=find) | - | | | [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject), [saved_objects_finder.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/public/components/markdown_editor/plugins/lens/saved_objects_finder.tsx#:~:text=SimpleSavedObject)+ 1 more | - | | | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/cases/common/ui/types.ts#:~:text=ResolvedSimpleSavedObject) | - | @@ -752,11 +752,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [discover_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/services/discover_state.ts#:~:text=syncQueryStateWithUrl), [discover_state.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/services/discover_state.ts#:~:text=syncQueryStateWithUrl) | - | | | [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject) | - | | | [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject), [get_layout_props.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/layout/__stories__/get_layout_props.ts#:~:text=SavedObject) | - | -| | [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title) | - | +| | [use_fetch_occurances_range.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title), [use_fetch_occurances_range.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title) | - | | | [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=create), [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=create) | - | | | [fetch_hits_in_interval.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts#:~:text=EsQuerySearchAfter), [fetch_hits_in_interval.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/context/utils/fetch_hits_in_interval.ts#:~:text=EsQuerySearchAfter), [get_es_query_search_after.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/context/utils/get_es_query_search_after.ts#:~:text=EsQuerySearchAfter), [get_es_query_search_after.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/context/utils/get_es_query_search_after.ts#:~:text=EsQuerySearchAfter), [get_es_query_search_after.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/context/utils/get_es_query_search_after.ts#:~:text=EsQuerySearchAfter) | - | -| | [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title) | - | -| | [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title) | - | +| | [use_fetch_occurances_range.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title), [use_fetch_occurances_range.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title) | - | +| | [use_fetch_occurances_range.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts#:~:text=title), [use_text_based_query_language.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/hooks/use_text_based_query_language.ts#:~:text=title) | - | | | [on_save_search.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx#:~:text=SavedObjectSaveModal), [on_save_search.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx#:~:text=SavedObjectSaveModal) | 8.8.0 | | | [saved_search_embeddable.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/embeddable/saved_search_embeddable.tsx#:~:text=executeTriggerActions), [search_embeddable_factory.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/embeddable/search_embeddable_factory.ts#:~:text=executeTriggerActions), [plugin.tsx](https://github.com/elastic/kibana/tree/main/src/plugins/discover/public/plugin.tsx#:~:text=executeTriggerActions) | - | | | [ui_settings.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/server/ui_settings.ts#:~:text=metric), [ui_settings.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/server/ui_settings.ts#:~:text=metric), [ui_settings.ts](https://github.com/elastic/kibana/tree/main/src/plugins/discover/server/ui_settings.ts#:~:text=metric) | - | @@ -976,7 +976,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectsCreateOptions), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectsCreateOptions), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectsCreateOptions), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectsCreateOptions) | - | | | [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectsBatchResponse), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectsBatchResponse) | - | | | [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectAttributes), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectAttributes), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectAttributes), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectAttributes), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectAttributes), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=SavedObjectAttributes), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=SavedObjectAttributes), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectAttributes), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectAttributes), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectAttributes)+ 2 more | - | -| | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [memory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts#:~:text=SavedObjectReference), [memory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts#:~:text=SavedObjectReference), [rx.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts#:~:text=SavedObjectReference), [rx.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts#:~:text=SavedObjectReference)+ 6 more | - | +| | [log_threshold_references_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts#:~:text=SavedObjectReference), [log_threshold_references_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts#:~:text=SavedObjectReference), [log_threshold_references_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [memory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts#:~:text=SavedObjectReference)+ 11 more | - | | | [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=savedObjects), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=savedObjects), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=savedObjects), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=savedObjects), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=savedObjects), [use_delete_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx#:~:text=savedObjects), [use_delete_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx#:~:text=savedObjects), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=savedObjects), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=savedObjects), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=savedObjects)+ 1 more | - | | | [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=savedObjects), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=savedObjects), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=savedObjects), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=savedObjects), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=savedObjects), [use_delete_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx#:~:text=savedObjects), [use_delete_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx#:~:text=savedObjects), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=savedObjects), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=savedObjects), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=savedObjects)+ 1 more | - | | | [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=create) | - | @@ -988,7 +988,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectsCreateOptions), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectsCreateOptions), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectsCreateOptions), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectsCreateOptions) | - | | | [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectsBatchResponse), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectsBatchResponse) | - | | | [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectAttributes), [use_find_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx#:~:text=SavedObjectAttributes), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectAttributes), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectAttributes), [use_create_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx#:~:text=SavedObjectAttributes), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=SavedObjectAttributes), [use_get_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_get_saved_object.tsx#:~:text=SavedObjectAttributes), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectAttributes), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectAttributes), [use_update_saved_object.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx#:~:text=SavedObjectAttributes)+ 2 more | - | -| | [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [memory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts#:~:text=SavedObjectReference), [memory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts#:~:text=SavedObjectReference), [rx.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts#:~:text=SavedObjectReference), [rx.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/rx.ts#:~:text=SavedObjectReference)+ 6 more | - | +| | [log_threshold_references_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts#:~:text=SavedObjectReference), [log_threshold_references_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts#:~:text=SavedObjectReference), [log_threshold_references_manager.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [types.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/types.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [cpu.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/cpu.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [load.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/load.ts#:~:text=SavedObjectReference), [memory.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/infra/public/common/visualizations/lens/hosts/memory.ts#:~:text=SavedObjectReference)+ 11 more | - | @@ -1506,16 +1506,16 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/ | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=create), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=create) | - | | | [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=bulkDelete) | - | -| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=find), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts#:~:text=find) | - | +| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=find), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=get), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=get) | - | | | [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=bulkResolve), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=bulkResolve) | - | | | [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject) | - | -| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts#:~:text=savedObjects)+ 1 more | - | -| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts#:~:text=savedObjects)+ 1 more | - | +| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects)+ 1 more | - | +| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=savedObjects), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=savedObjects), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=savedObjects), [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=savedObjects), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=savedObjects), [use_locations_api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts#:~:text=savedObjects), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=savedObjects)+ 1 more | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=SavedObjectsClientContract) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=create), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=create) | - | | | [delete_param.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx#:~:text=bulkDelete) | - | -| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=find), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts#:~:text=find) | - | +| | [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/use_monitor_name.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/manage_locations/hooks/use_location_monitors.ts#:~:text=find), [use_filters.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts#:~:text=find), [use_location_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts#:~:text=find), [use_monitor_name.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find), [use_filters.test.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts#:~:text=find) | - | | | [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/state/private_locations/api.ts#:~:text=get), [api.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/api.ts#:~:text=get) | - | | | [use_invalid_monitors.tsx](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/hooks/use_invalid_monitors.tsx#:~:text=bulkResolve), [use_recently_viewed_monitors.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts#:~:text=bulkResolve) | - | | | [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject), [synthetics_monitor.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/synthetics/common/types/synthetics_monitor.ts#:~:text=SimpleSavedObject) | - | diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index f30544104b267..d68a5ca0d423b 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 021378947c8ee..0dc280c086702 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.devdocs.json b/api_docs/discover.devdocs.json index 438a91e5d2171..5a808736cc9b2 100644 --- a/api_docs/discover.devdocs.json +++ b/api_docs/discover.devdocs.json @@ -478,7 +478,7 @@ "section": "def-common.SearchSourceFields", "text": "SearchSourceFields" }, - "[K]; getActiveIndexFilter: () => string[]; getOwnField: any[]; getOwnField: >", + "label": "[key: string]: Partial Promise<", + " | undefined; } | undefined, context?: ", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined, request?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -6073,6 +6095,50 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.create.$5", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.create.$6", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -7086,7 +7152,23 @@ "section": "def-common.AuthenticatedUser", "text": "AuthenticatedUser" }, - " | undefined; skipUnassignFromAgentPolicies?: boolean | undefined; force?: boolean | undefined; } | undefined) => Promise<", + " | undefined; skipUnassignFromAgentPolicies?: boolean | undefined; force?: boolean | undefined; } | undefined, context?: ", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined, request?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -7218,6 +7300,50 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.delete.$5", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.delete.$6", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -7743,6 +7869,14 @@ "section": "def-common.PackagePolicy", "text": "PackagePolicy" }, + " : A extends \"packagePolicyUpdate\" ? ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.UpdatePackagePolicy", + "text": "UpdatePackagePolicy" + }, " : ", { "pluginId": "fleet", @@ -7751,7 +7885,23 @@ "section": "def-common.NewPackagePolicy", "text": "NewPackagePolicy" }, - ", context: ", + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", { "pluginId": "@kbn/core-http-request-handler-context-server", "scope": "common", @@ -7759,7 +7909,7 @@ "section": "def-common.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", request: ", + " | undefined, request?: ", { "pluginId": "@kbn/core-http-server", "scope": "common", @@ -7767,7 +7917,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ") => Promise | undefined) => Promise" + " | undefined" ], "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "isRequired": false } ], "returnComment": [] @@ -7900,7 +8109,39 @@ "signature": [ "(deletedPackagePolicies: ", "DeletePackagePoliciesResponse", - ") => Promise" + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined, request?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined) => Promise" ], "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", "deprecated": false, @@ -7920,38 +8161,156 @@ "deprecated": false, "trackAdoption": false, "isRequired": true - } - ], - "returnComment": [] - }, - { - "parentPluginId": "fleet", - "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks", - "type": "Function", - "tags": [], - "label": "runPostDeleteExternalCallbacks", - "description": [], - "signature": [ - "(deletedPackagePolicies: ", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PostDeletePackagePoliciesResponse", - "text": "PostDeletePackagePoliciesResponse" }, - ") => Promise" - ], - "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ { "parentPluginId": "fleet", - "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks.$1", - "type": "Array", + "id": "def-server.PackagePolicyClient.runDeleteExternalCallbacks.$2", + "type": "Object", "tags": [], - "label": "deletedPackagePolicies", + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runDeleteExternalCallbacks.$3", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runDeleteExternalCallbacks.$4", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runDeleteExternalCallbacks.$5", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks", + "type": "Function", + "tags": [], + "label": "runPostDeleteExternalCallbacks", + "description": [], + "signature": [ + "(deletedPackagePolicies: ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PostDeletePackagePoliciesResponse", + "text": "PostDeletePackagePoliciesResponse" + }, + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined, request?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined) => Promise" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks.$1", + "type": "Array", + "tags": [], + "label": "deletedPackagePolicies", "description": [], "signature": [ { @@ -7966,6 +8325,92 @@ "deprecated": false, "trackAdoption": false, "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks.$2", + "type": "Object", + "tags": [], + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks.$3", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + } + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks.$4", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PackagePolicyClient.runPostDeleteExternalCallbacks.$5", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/services/package_policy_service.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false } ], "returnComment": [] @@ -8214,7 +8659,23 @@ "section": "def-common.NewPackagePolicy", "text": "NewPackagePolicy" }, - ", context: ", + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", { "pluginId": "@kbn/core-http-request-handler-context-server", "scope": "common", @@ -8222,7 +8683,7 @@ "section": "def-common.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", request: ", + " | undefined, request?: ", { "pluginId": "@kbn/core-http-server", "scope": "common", @@ -8230,7 +8691,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ") => Promise<", + " | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -8270,15 +8731,15 @@ "id": "def-server.PostPackagePolicyCreateCallback.$2", "type": "Object", "tags": [], - "label": "context", + "label": "soClient", "description": [], "signature": [ { - "pluginId": "@kbn/core-http-request-handler-context-server", + "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", - "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", - "section": "def-common.RequestHandlerContext", - "text": "RequestHandlerContext" + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" } ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", @@ -8290,28 +8751,1249 @@ "id": "def-server.PostPackagePolicyCreateCallback.$3", "type": "Object", "tags": [], - "label": "request", + "label": "esClient", "description": [], "signature": [ - { - "pluginId": "@kbn/core-http-server", - "scope": "common", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-common.KibanaRequest", - "text": "KibanaRequest" - }, - "" - ], - "path": "x-pack/plugins/fleet/server/types/extensions.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyDeleteCallback", + "{ name: string | symbol; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; cluster: ", + "default", + "; eql: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; monitoring: ", + "default", + "; security: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; asyncSearch: ", + "default", + "; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kRollup]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; helpers: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; ilm: ", + "default", + "; indices: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyCreateCallback.$4", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyCreateCallback.$5", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyDeleteCallback", "type": "Type", "tags": [], "label": "PostPackagePolicyDeleteCallback", @@ -8319,7 +10001,39 @@ "signature": [ "(packagePolicies: ", "DeletePackagePoliciesResponse", - ") => Promise" + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined, request?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined) => Promise" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -8328,20 +10042,2630 @@ "children": [ { "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyDeleteCallback.$1", - "type": "Array", + "id": "def-server.PostPackagePolicyDeleteCallback.$1", + "type": "Array", + "tags": [], + "label": "packagePolicies", + "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackagePolicy", + "text": "PackagePolicy" + }, + "[]" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyDeleteCallback.$2", + "type": "Object", + "tags": [], + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyDeleteCallback.$3", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + "{ name: string | symbol; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; cluster: ", + "default", + "; eql: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; monitoring: ", + "default", + "; security: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; asyncSearch: ", + "default", + "; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kRollup]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; helpers: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; ilm: ", + "default", + "; indices: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyDeleteCallback.$4", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyDeleteCallback.$5", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostCreateCallback", + "type": "Type", + "tags": [], + "label": "PostPackagePolicyPostCreateCallback", + "description": [], + "signature": [ + "(packagePolicy: ", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackagePolicy", + "text": "PackagePolicy" + }, + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined, request?: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + " | undefined) => Promise<", + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackagePolicy", + "text": "PackagePolicy" + }, + ">" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostCreateCallback.$1", + "type": "Object", + "tags": [], + "label": "packagePolicy", + "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.PackagePolicy", + "text": "PackagePolicy" + } + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostCreateCallback.$2", + "type": "Object", + "tags": [], + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostCreateCallback.$3", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + "{ name: string | symbol; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; cluster: ", + "default", + "; eql: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; monitoring: ", + "default", + "; security: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; asyncSearch: ", + "default", + "; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kRollup]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; helpers: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; ilm: ", + "default", + "; indices: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostCreateCallback.$4", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostCreateCallback.$5", + "type": "Object", "tags": [], - "label": "packagePolicies", + "label": "request", "description": [], "signature": [ { - "pluginId": "fleet", + "pluginId": "@kbn/core-http-server", "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" }, - "[]" + " | undefined" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -8352,21 +12676,39 @@ }, { "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyPostCreateCallback", + "id": "def-server.PostPackagePolicyPostDeleteCallback", "type": "Type", "tags": [], - "label": "PostPackagePolicyPostCreateCallback", + "label": "PostPackagePolicyPostDeleteCallback", "description": [], "signature": [ - "(packagePolicy: ", + "(deletedPackagePolicies: ", + "_DeepReadonlyArray", + "<{ id: string; name?: string | undefined; success: boolean; package?: ", { "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" + "section": "def-common.PackagePolicyPackage", + "text": "PackagePolicyPackage" + }, + " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>, soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" }, - ", context: ", + ", context?: ", { "pluginId": "@kbn/core-http-request-handler-context-server", "scope": "common", @@ -8374,7 +12716,7 @@ "section": "def-common.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", request: ", + " | undefined, request?: ", { "pluginId": "@kbn/core-http-server", "scope": "common", @@ -8382,15 +12724,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ") => Promise<", - { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - }, - ">" + " | undefined) => Promise" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -8399,19 +12733,22 @@ "children": [ { "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyPostCreateCallback.$1", + "id": "def-server.PostPackagePolicyPostDeleteCallback.$1", "type": "Object", "tags": [], - "label": "packagePolicy", + "label": "deletedPackagePolicies", "description": [], "signature": [ + "_DeepReadonlyArray", + "<{ id: string; name?: string | undefined; success: boolean; package?: ", { "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicy", - "text": "PackagePolicy" - } + "section": "def-common.PackagePolicyPackage", + "text": "PackagePolicyPackage" + }, + " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -8419,18 +12756,18 @@ }, { "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyPostCreateCallback.$2", + "id": "def-server.PostPackagePolicyPostDeleteCallback.$2", "type": "Object", "tags": [], - "label": "context", + "label": "soClient", "description": [], "signature": [ { - "pluginId": "@kbn/core-http-request-handler-context-server", + "pluginId": "@kbn/core-saved-objects-api-server", "scope": "common", - "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", - "section": "def-common.RequestHandlerContext", - "text": "RequestHandlerContext" + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" } ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", @@ -8439,71 +12776,1241 @@ }, { "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyPostCreateCallback.$3", + "id": "def-server.PostPackagePolicyPostDeleteCallback.$3", "type": "Object", "tags": [], - "label": "request", + "label": "esClient", "description": [], "signature": [ - { - "pluginId": "@kbn/core-http-server", - "scope": "common", - "docId": "kibKbnCoreHttpServerPluginApi", - "section": "def-common.KibanaRequest", - "text": "KibanaRequest" - }, - "" + "{ name: string | symbol; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; cluster: ", + "default", + "; eql: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; monitoring: ", + "default", + "; security: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; asyncSearch: ", + "default", + "; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kRollup]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; helpers: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; ilm: ", + "default", + "; indices: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, "trackAdoption": false - } - ], - "initialIsOpen": false - }, - { - "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyPostDeleteCallback", - "type": "Type", - "tags": [], - "label": "PostPackagePolicyPostDeleteCallback", - "description": [], - "signature": [ - "(deletedPackagePolicies: ", - "_DeepReadonlyArray", - "<{ id: string; name?: string | undefined; success: boolean; package?: ", + }, { - "pluginId": "fleet", - "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicyPackage", - "text": "PackagePolicyPackage" + "parentPluginId": "fleet", + "id": "def-server.PostPackagePolicyPostDeleteCallback.$4", + "type": "Object", + "tags": [], + "label": "context", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-request-handler-context-server", + "scope": "common", + "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", + "section": "def-common.RequestHandlerContext", + "text": "RequestHandlerContext" + }, + " | undefined" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false }, - " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>) => Promise" - ], - "path": "x-pack/plugins/fleet/server/types/extensions.ts", - "deprecated": false, - "trackAdoption": false, - "returnComment": [], - "children": [ { "parentPluginId": "fleet", - "id": "def-server.PostPackagePolicyPostDeleteCallback.$1", + "id": "def-server.PostPackagePolicyPostDeleteCallback.$5", "type": "Object", "tags": [], - "label": "deletedPackagePolicies", + "label": "request", "description": [], "signature": [ - "_DeepReadonlyArray", - "<{ id: string; name?: string | undefined; success: boolean; package?: ", { - "pluginId": "fleet", + "pluginId": "@kbn/core-http-server", "scope": "common", - "docId": "kibFleetPluginApi", - "section": "def-common.PackagePolicyPackage", - "text": "PackagePolicyPackage" + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" }, - " | undefined; policy_id?: string | undefined; statusCode?: number | undefined; body?: { message: string; } | undefined; }>" + " | undefined" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -8528,7 +14035,23 @@ "section": "def-common.UpdatePackagePolicy", "text": "UpdatePackagePolicy" }, - ", context: ", + ", soClient: ", + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + }, + ", esClient: ", + { + "pluginId": "@kbn/core-elasticsearch-server", + "scope": "common", + "docId": "kibKbnCoreElasticsearchServerPluginApi", + "section": "def-common.ElasticsearchClient", + "text": "ElasticsearchClient" + }, + ", context?: ", { "pluginId": "@kbn/core-http-request-handler-context-server", "scope": "common", @@ -8536,7 +14059,7 @@ "section": "def-common.RequestHandlerContext", "text": "RequestHandlerContext" }, - ", request: ", + " | undefined, request?: ", { "pluginId": "@kbn/core-http-server", "scope": "common", @@ -8544,7 +14067,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ") => Promise<", + " | undefined) => Promise<", { "pluginId": "fleet", "scope": "common", @@ -8584,6 +14107,1226 @@ "id": "def-server.PutPackagePolicyUpdateCallback.$2", "type": "Object", "tags": [], + "label": "soClient", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-saved-objects-api-server", + "scope": "common", + "docId": "kibKbnCoreSavedObjectsApiServerPluginApi", + "section": "def-common.SavedObjectsClientContract", + "text": "SavedObjectsClientContract" + } + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PutPackagePolicyUpdateCallback.$3", + "type": "Object", + "tags": [], + "label": "esClient", + "description": [], + "signature": [ + "{ name: string | symbol; get: { (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetResponse", + ">; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetResponse", + ", unknown>>; (this: That, params: ", + "GetRequest", + " | ", + "GetRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetResponse", + ">; }; delete: { (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteRequest", + " | ", + "DeleteRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; cluster: ", + "default", + "; eql: ", + "default", + "; search: { >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchResponse", + ">; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchResponse", + ", unknown>>; >(this: That, params?: ", + "SearchRequest", + " | ", + "SearchRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchResponse", + ">; }; create: { (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "CreateRequest", + " | ", + "CreateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; monitoring: ", + "default", + "; security: ", + "default", + "; index: { (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "WriteResponseBase", + ">; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "WriteResponseBase", + ", unknown>>; (this: That, params: ", + "IndexRequest", + " | ", + "IndexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "WriteResponseBase", + ">; }; update: { (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateResponse", + ">; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateResponse", + ", unknown>>; (this: That, params: ", + "UpdateRequest", + " | ", + "UpdateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateResponse", + ">; }; asyncSearch: ", + "default", + "; [kAsyncSearch]: symbol | null; [kAutoscaling]: symbol | null; [kCat]: symbol | null; [kCcr]: symbol | null; [kCluster]: symbol | null; [kDanglingIndices]: symbol | null; [kEnrich]: symbol | null; [kEql]: symbol | null; [kFeatures]: symbol | null; [kFleet]: symbol | null; [kGraph]: symbol | null; [kIlm]: symbol | null; [kIndices]: symbol | null; [kIngest]: symbol | null; [kLicense]: symbol | null; [kLogstash]: symbol | null; [kMigration]: symbol | null; [kMl]: symbol | null; [kMonitoring]: symbol | null; [kNodes]: symbol | null; [kRollup]: symbol | null; [kSearchableSnapshots]: symbol | null; [kSecurity]: symbol | null; [kShutdown]: symbol | null; [kSlm]: symbol | null; [kSnapshot]: symbol | null; [kSql]: symbol | null; [kSsl]: symbol | null; [kTasks]: symbol | null; [kTextStructure]: symbol | null; [kTransform]: symbol | null; [kWatcher]: symbol | null; [kXpack]: symbol | null; transport: ", + "default", + "; helpers: ", + "default", + "; child: (opts: ", + "ClientOptions", + ") => ", + "default", + "; autoscaling: ", + "default", + "; bulk: { (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "BulkResponse", + ">; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "BulkResponse", + ", unknown>>; (this: That, params: ", + "BulkRequest", + " | ", + "BulkRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "BulkResponse", + ">; }; cat: ", + "default", + "; ccr: ", + "default", + "; clearScroll: { (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClearScrollResponse", + ">; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClearScrollResponse", + ", unknown>>; (this: That, params?: ", + "ClearScrollRequest", + " | ", + "ClearScrollRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClearScrollResponse", + ">; }; closePointInTime: { (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ClosePointInTimeResponse", + ", unknown>>; (this: That, params: ", + "ClosePointInTimeRequest", + " | ", + "ClosePointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ClosePointInTimeResponse", + ">; }; count: { (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "CountResponse", + ">; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "CountResponse", + ", unknown>>; (this: That, params?: ", + "CountRequest", + " | ", + "CountRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "CountResponse", + ">; }; danglingIndices: ", + "default", + "; deleteByQuery: { (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "DeleteByQueryResponse", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRequest", + " | ", + "DeleteByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "DeleteByQueryResponse", + ">; }; deleteByQueryRethrottle: { (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TasksTaskListResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteByQueryRethrottleRequest", + " | ", + "DeleteByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TasksTaskListResponseBase", + ">; }; deleteScript: { (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "DeleteScriptRequest", + " | ", + "DeleteScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; enrich: ", + "default", + "; exists: { (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsRequest", + " | ", + "ExistsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; existsSource: { (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "ExistsSourceRequest", + " | ", + "ExistsSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; explain: { (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ExplainResponse", + ">; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ExplainResponse", + ", unknown>>; (this: That, params: ", + "ExplainRequest", + " | ", + "ExplainRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ExplainResponse", + ">; }; features: ", + "default", + "; fieldCaps: { (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "FieldCapsResponse", + ">; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "FieldCapsResponse", + ", unknown>>; (this: That, params: ", + "FieldCapsRequest", + " | ", + "FieldCapsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "FieldCapsResponse", + ">; }; fleet: ", + "default", + "; getScript: { (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptResponse", + ">; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptResponse", + ", unknown>>; (this: That, params: ", + "GetScriptRequest", + " | ", + "GetScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptResponse", + ">; }; getScriptContext: { (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptContextResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptContextRequest", + " | ", + "GetScriptContextRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptContextResponse", + ">; }; getScriptLanguages: { (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "GetScriptLanguagesResponse", + ", unknown>>; (this: That, params?: ", + "GetScriptLanguagesRequest", + " | ", + "GetScriptLanguagesRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "GetScriptLanguagesResponse", + ">; }; getSource: { (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "GetSourceRequest", + " | ", + "GetSourceRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; graph: ", + "default", + "; ilm: ", + "default", + "; indices: ", + "default", + "; info: { (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "InfoResponse", + ">; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "InfoResponse", + ", unknown>>; (this: That, params?: ", + "InfoRequest", + " | ", + "InfoRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "InfoResponse", + ">; }; ingest: ", + "default", + "; knnSearch: { (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "KnnSearchResponse", + ">; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "KnnSearchResponse", + ", unknown>>; (this: That, params: ", + "KnnSearchRequest", + " | ", + "KnnSearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "KnnSearchResponse", + ">; }; license: ", + "default", + "; logstash: ", + "default", + "; mget: { (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MgetResponse", + ">; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MgetResponse", + ", unknown>>; (this: That, params?: ", + "MgetRequest", + " | ", + "MgetRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MgetResponse", + ">; }; migration: ", + "default", + "; ml: ", + "default", + "; msearch: { >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchResponse", + ">; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchResponse", + ", unknown>>; >(this: That, params: ", + "MsearchRequest", + " | ", + "MsearchRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchResponse", + ">; }; msearchTemplate: { >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MsearchTemplateResponse", + ", unknown>>; >(this: That, params: ", + "MsearchTemplateRequest", + " | ", + "MsearchTemplateRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MsearchTemplateResponse", + ">; }; mtermvectors: { (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "MtermvectorsResponse", + ", unknown>>; (this: That, params?: ", + "MtermvectorsRequest", + " | ", + "MtermvectorsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "MtermvectorsResponse", + ">; }; nodes: ", + "default", + "; openPointInTime: { (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "OpenPointInTimeResponse", + ", unknown>>; (this: That, params: ", + "OpenPointInTimeRequest", + " | ", + "OpenPointInTimeRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "OpenPointInTimeResponse", + ">; }; ping: { (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params?: ", + "PingRequest", + " | ", + "PingRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; putScript: { (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "AcknowledgedResponseBase", + ", unknown>>; (this: That, params: ", + "PutScriptRequest", + " | ", + "PutScriptRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "AcknowledgedResponseBase", + ">; }; rankEval: { (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RankEvalResponse", + ">; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RankEvalResponse", + ", unknown>>; (this: That, params: ", + "RankEvalRequest", + " | ", + "RankEvalRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RankEvalResponse", + ">; }; reindex: { (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexResponse", + ">; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexResponse", + ", unknown>>; (this: That, params: ", + "ReindexRequest", + " | ", + "ReindexRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexResponse", + ">; }; reindexRethrottle: { (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ReindexRethrottleResponse", + ", unknown>>; (this: That, params: ", + "ReindexRethrottleRequest", + " | ", + "ReindexRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ReindexRethrottleResponse", + ">; }; renderSearchTemplate: { (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "RenderSearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "RenderSearchTemplateRequest", + " | ", + "RenderSearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "RenderSearchTemplateResponse", + ">; }; rollup: ", + "default", + "; scriptsPainlessExecute: { (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScriptsPainlessExecuteResponse", + ", unknown>>; (this: That, params?: ", + "ScriptsPainlessExecuteRequest", + " | ", + "ScriptsPainlessExecuteRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScriptsPainlessExecuteResponse", + ">; }; scroll: { >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "ScrollResponse", + ">; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "ScrollResponse", + ", unknown>>; >(this: That, params: ", + "ScrollRequest", + " | ", + "ScrollRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "ScrollResponse", + ">; }; searchMvt: { (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + ">; (this: That, params: ", + "SearchMvtRequest", + " | ", + "SearchMvtRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise; }; searchShards: { (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchShardsResponse", + ">; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchShardsResponse", + ", unknown>>; (this: That, params?: ", + "SearchShardsRequest", + " | ", + "SearchShardsRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchShardsResponse", + ">; }; searchTemplate: { (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "SearchTemplateResponse", + ", unknown>>; (this: That, params?: ", + "SearchTemplateRequest", + " | ", + "SearchTemplateRequest", + " | undefined, options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "SearchTemplateResponse", + ">; }; searchableSnapshots: ", + "default", + "; shutdown: ", + "default", + "; slm: ", + "default", + "; snapshot: ", + "default", + "; sql: ", + "default", + "; ssl: ", + "default", + "; tasks: ", + "default", + "; termsEnum: { (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermsEnumResponse", + ">; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermsEnumResponse", + ", unknown>>; (this: That, params: ", + "TermsEnumRequest", + " | ", + "TermsEnumRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermsEnumResponse", + ">; }; termvectors: { (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "TermvectorsResponse", + ">; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "TermvectorsResponse", + ", unknown>>; (this: That, params: ", + "TermvectorsRequest", + " | ", + "TermvectorsRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "TermvectorsResponse", + ">; }; textStructure: ", + "default", + "; transform: ", + "default", + "; updateByQuery: { (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRequest", + " | ", + "UpdateByQueryRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryResponse", + ">; }; updateByQueryRethrottle: { (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithOutMeta", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptionsWithMeta", + " | undefined): Promise<", + "TransportResult", + "<", + "UpdateByQueryRethrottleResponse", + ", unknown>>; (this: That, params: ", + "UpdateByQueryRethrottleRequest", + " | ", + "UpdateByQueryRethrottleRequest", + ", options?: ", + "TransportRequestOptions", + " | undefined): Promise<", + "UpdateByQueryRethrottleResponse", + ">; }; watcher: ", + "default", + "; xpack: ", + "default", + "; }" + ], + "path": "x-pack/plugins/fleet/server/types/extensions.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "fleet", + "id": "def-server.PutPackagePolicyUpdateCallback.$4", + "type": "Object", + "tags": [], "label": "context", "description": [], "signature": [ @@ -8593,7 +15336,8 @@ "docId": "kibKbnCoreHttpRequestHandlerContextServerPluginApi", "section": "def-common.RequestHandlerContext", "text": "RequestHandlerContext" - } + }, + " | undefined" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -8601,7 +15345,7 @@ }, { "parentPluginId": "fleet", - "id": "def-server.PutPackagePolicyUpdateCallback.$3", + "id": "def-server.PutPackagePolicyUpdateCallback.$5", "type": "Object", "tags": [], "label": "request", @@ -8614,7 +15358,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - "" + " | undefined" ], "path": "x-pack/plugins/fleet/server/types/extensions.ts", "deprecated": false, @@ -11208,31 +17952,42 @@ "children": [ { "parentPluginId": "fleet", - "id": "def-common.GetAgentsResponse.totalInactive", - "type": "number", + "id": "def-common.GetAgentsResponse.list", + "type": "Array", "tags": [], - "label": "totalInactive", + "label": "list", "description": [], + "signature": [ + { + "pluginId": "fleet", + "scope": "common", + "docId": "kibFleetPluginApi", + "section": "def-common.Agent", + "text": "Agent" + }, + "[] | undefined" + ], "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "fleet", - "id": "def-common.GetAgentsResponse.list", - "type": "Array", + "id": "def-common.GetAgentsResponse.statusSummary", + "type": "Object", "tags": [], - "label": "list", + "label": "statusSummary", "description": [], "signature": [ + "Record<", { "pluginId": "fleet", "scope": "common", "docId": "kibFleetPluginApi", - "section": "def-common.Agent", - "text": "Agent" + "section": "def-common.AgentStatus", + "text": "AgentStatus" }, - "[] | undefined" + ", number> | undefined" ], "path": "x-pack/plugins/fleet/common/types/rest_spec/agent.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index b899d6d2de663..9e197485fbf2a 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Fleet](https://github.com/orgs/elastic/teams/fleet) for questions regar | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 1040 | 3 | 935 | 25 | +| 1068 | 3 | 963 | 26 | ## Client diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index bcf7038f4b851..7c16d27797d76 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 4d246c79461e5..27f68a3254ce7 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 4854c9abced73..0507c50616f39 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/image_embeddable.mdx b/api_docs/image_embeddable.mdx index a6699d0e86c3d..7ede4ab81b28f 100644 --- a/api_docs/image_embeddable.mdx +++ b/api_docs/image_embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/imageEmbeddable title: "imageEmbeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the imageEmbeddable plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'imageEmbeddable'] --- import imageEmbeddableObj from './image_embeddable.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index 1d9adb71ac0e5..05b8b99d16587 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 1ceea6db43660..66b0ec7d4064f 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index eb094011f7443..d2454b0f00b38 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 8f1d5f0129a8a..598bad6c3ecff 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 50efdb79e6cfb..771ad0487b1cf 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index e9a76aa665eae..de45dee7e15a3 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 8f0fd576c413b..ec436b12a789a 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index 7d18a038dbd52..a81650fa04a47 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index 24a185f0bcafb..fb4e4a24d0956 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index fb7772ac2e53a..244fff7c177e7 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index b590e8eff06d7..8e6292796ba57 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index 0d15a40c8abed..e2896b8351cc9 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,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 description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index ad86db69ebf2a..1495cb62a5629 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,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 description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index db8d903c06614..a601217b3bba4 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,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 description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index 39db1ca3e617c..1ef8a71783b53 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_gainsight.mdx b/api_docs/kbn_analytics_shippers_gainsight.mdx index 1e7983eba24b8..ecaa539fb20f0 100644 --- a/api_docs/kbn_analytics_shippers_gainsight.mdx +++ b/api_docs/kbn_analytics_shippers_gainsight.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-gainsight title: "@kbn/analytics-shippers-gainsight" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-gainsight plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-gainsight'] --- import kbnAnalyticsShippersGainsightObj from './kbn_analytics_shippers_gainsight.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index b60512f023b35..f490cb8efa1c0 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 186c425d73057..398364e141fb0 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace_client.devdocs.json b/api_docs/kbn_apm_synthtrace_client.devdocs.json index b0422331cb101..7c272f970dbc0 100644 --- a/api_docs/kbn_apm_synthtrace_client.devdocs.json +++ b/api_docs/kbn_apm_synthtrace_client.devdocs.json @@ -2318,7 +2318,7 @@ "GeoLocation", "; 'client.geo.region_iso_code': string; 'client.geo.region_name': string; 'client.ip': string; 'cloud.account.id': string; 'cloud.account.name': string; 'cloud.availability_zone': string; 'cloud.machine.type': string; 'cloud.project.id': string; 'cloud.project.name': string; 'cloud.provider': string; 'cloud.region': string; 'cloud.service.name': string; 'container.id': string; 'destination.address': string; 'destination.port': number; 'device.id': string; 'device.manufacturer': string; 'device.model.identifier': string; 'device.model.name': string; 'ecs.version': string; 'error.exception': ", "ApmException", - "[]; 'error.grouping_key': string; 'error.grouping_name': string; 'error.id': string; 'event.ingested': number; 'event.name': string; 'event.outcome': string; 'event.outcome_numeric': number | { sum: number; value_count: number; }; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.id': string; 'faas.name': string; 'faas.trigger.type': string; 'faas.version': string; 'host.architecture': string; 'host.hostname': string; 'host.name': string; 'host.os.full': string; 'host.os.name': string; 'host.os.platform': string; 'host.os.type': string; 'host.os.version': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.name': string; 'kubernetes.pod.uid': string; 'metricset.name': string; 'network.carrier.icc': string; 'network.carrier.mcc': string; 'network.carrier.mnc': string; 'network.carrier.name': string; 'network.connection.subtype': string; 'network.connection.type': string; 'observer.type': string; 'observer.version_major': number; 'observer.version': string; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'session.id': string; 'trace.id': string; 'transaction.duration.us': number; 'transaction.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'service.environment': string; 'service.framework.name': string; 'service.framework.version': string; 'service.language.name': string; 'service.language.version': string; 'service.name': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.target.name': string; 'service.target.type': string; 'service.version': string; 'span.action': string; 'span.destination.service.resource': string; 'span.destination.service.response_time.count': number; 'span.destination.service.response_time.sum.us': number; 'span.duration.us': number; 'span.id': string; 'span.name': string; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.subtype': string; 'span.type': string; 'transaction.result': string; 'transaction.sampled': true; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'url.original': string; }> & Partial<{ 'system.process.memory.size': number; 'system.memory.actual.free': number; 'system.memory.total': number; 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; 'jvm.memory.heap.used': number; 'jvm.memory.non_heap.used': number; 'jvm.thread.count': number; 'faas.billed_duration': number; 'faas.timeout': number; 'faas.coldstart_duration': number; 'faas.duration': number; }> & Partial<{ 'metricset.interval': string; 'transaction.duration.summary': string; }>" + "[]; 'error.grouping_key': string; 'error.grouping_name': string; 'error.id': string; 'event.ingested': number; 'event.name': string; 'event.outcome': string; 'event.outcome_numeric': number | { sum: number; value_count: number; }; 'faas.coldstart': boolean; 'faas.execution': string; 'faas.id': string; 'faas.name': string; 'faas.trigger.type': string; 'faas.version': string; 'host.architecture': string; 'host.hostname': string; 'host.name': string; 'host.os.full': string; 'host.os.name': string; 'host.os.platform': string; 'host.os.type': string; 'host.os.version': string; 'http.request.method': string; 'http.response.status_code': number; 'kubernetes.pod.name': string; 'kubernetes.pod.uid': string; 'metricset.name': string; 'network.carrier.icc': string; 'network.carrier.mcc': string; 'network.carrier.mnc': string; 'network.carrier.name': string; 'network.connection.subtype': string; 'network.connection.type': string; 'observer.type': string; 'observer.version_major': number; 'observer.version': string; 'parent.id': string; 'processor.event': string; 'processor.name': string; 'session.id': string; 'trace.id': string; 'transaction.aggregation.overflow_count': number; 'transaction.duration.us': number; 'transaction.id': string; 'transaction.name': string; 'transaction.type': string; 'transaction.duration.histogram': { values: number[]; counts: number[]; }; 'service.environment': string; 'service.framework.name': string; 'service.framework.version': string; 'service.language.name': string; 'service.language.version': string; 'service.name': string; 'service.node.name': string; 'service.runtime.name': string; 'service.runtime.version': string; 'service.target.name': string; 'service.target.type': string; 'service.version': string; 'span.action': string; 'span.destination.service.resource': string; 'span.destination.service.response_time.count': number; 'span.destination.service.response_time.sum.us': number; 'span.duration.us': number; 'span.id': string; 'span.name': string; 'span.self_time.count': number; 'span.self_time.sum.us': number; 'span.subtype': string; 'span.type': string; 'transaction.result': string; 'transaction.sampled': true; 'span.links': { trace: { id: string; }; span: { id: string; }; }[]; 'url.original': string; }> & Partial<{ 'system.process.memory.size': number; 'system.memory.actual.free': number; 'system.memory.total': number; 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; 'jvm.memory.heap.used': number; 'jvm.memory.non_heap.used': number; 'jvm.thread.count': number; 'faas.billed_duration': number; 'faas.timeout': number; 'faas.coldstart_duration': number; 'faas.duration': number; }> & Partial<{ 'metricset.interval': string; 'transaction.duration.summary': string; }>" ], "path": "packages/kbn-apm-synthtrace-client/src/lib/apm/apm_fields.ts", "deprecated": false, diff --git a/api_docs/kbn_apm_synthtrace_client.mdx b/api_docs/kbn_apm_synthtrace_client.mdx index 51fb8b8005858..71206324973a4 100644 --- a/api_docs/kbn_apm_synthtrace_client.mdx +++ b/api_docs/kbn_apm_synthtrace_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace-client title: "@kbn/apm-synthtrace-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace-client plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace-client'] --- import kbnApmSynthtraceClientObj from './kbn_apm_synthtrace_client.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index fbb95221542f5..6b8f82962e3ed 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 35c0fe796b9c5..f552890b3c0e8 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_cases_components.mdx b/api_docs/kbn_cases_components.mdx index e2eac6071fba4..5a6bf288b1740 100644 --- a/api_docs/kbn_cases_components.mdx +++ b/api_docs/kbn_cases_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cases-components title: "@kbn/cases-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cases-components plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cases-components'] --- import kbnCasesComponentsObj from './kbn_cases_components.devdocs.json'; diff --git a/api_docs/kbn_cell_actions.mdx b/api_docs/kbn_cell_actions.mdx index f0bc5b1fb97e3..bf409b6aa7ec8 100644 --- a/api_docs/kbn_cell_actions.mdx +++ b/api_docs/kbn_cell_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cell-actions title: "@kbn/cell-actions" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cell-actions plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cell-actions'] --- import kbnCellActionsObj from './kbn_cell_actions.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 2c4a3016cc511..67dcb198f75fe 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index 77e1342b6a7b8..821e9db5a538d 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index 9b4395d47a864..28c248974ef80 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index 4a84ced7636a9..d6039a50eb4a0 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index 6d1ff577044ab..6f943294752f5 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_code_editor.devdocs.json b/api_docs/kbn_code_editor.devdocs.json new file mode 100644 index 0000000000000..0bd40c08dd6c4 --- /dev/null +++ b/api_docs/kbn_code_editor.devdocs.json @@ -0,0 +1,65 @@ +{ + "id": "@kbn/code-editor", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/code-editor", + "id": "def-common.CodeEditor", + "type": "Function", + "tags": [], + "label": "CodeEditor", + "description": [], + "signature": [ + "({ languageId, value, onChange, width, options, overrideEditorWillMount, editorDidMount, editorWillMount, useDarkTheme, transparentBackground, suggestionProvider, signatureProvider, hoverProvider, placeholder, languageConfiguration, \"aria-label\": ariaLabel, isCopyable, allowFullScreen, }: React.PropsWithChildren<", + "Props", + ">) => JSX.Element" + ], + "path": "packages/shared-ux/code_editor/impl/code_editor.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor", + "id": "def-common.CodeEditor.$1", + "type": "CompoundType", + "tags": [], + "label": "{\n languageId,\n value,\n onChange,\n width,\n options,\n overrideEditorWillMount,\n editorDidMount,\n editorWillMount,\n useDarkTheme,\n transparentBackground,\n suggestionProvider,\n signatureProvider,\n hoverProvider,\n placeholder,\n languageConfiguration,\n 'aria-label': ariaLabel = i18n.translate('sharedUXPackages.codeEditor.ariaLabel', {\n defaultMessage: 'Code Editor',\n }),\n isCopyable = false,\n allowFullScreen = false,\n}", + "description": [], + "signature": [ + "React.PropsWithChildren<", + "Props", + ">" + ], + "path": "packages/shared-ux/code_editor/impl/code_editor.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_code_editor.mdx b/api_docs/kbn_code_editor.mdx new file mode 100644 index 0000000000000..3c502a3603284 --- /dev/null +++ b/api_docs/kbn_code_editor.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCodeEditorPluginApi +slug: /kibana-dev-docs/api/kbn-code-editor +title: "@kbn/code-editor" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/code-editor plugin +date: 2023-02-02 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor'] +--- +import kbnCodeEditorObj from './kbn_code_editor.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 1 | + +## Common + +### Functions + + diff --git a/api_docs/kbn_code_editor_mocks.devdocs.json b/api_docs/kbn_code_editor_mocks.devdocs.json new file mode 100644 index 0000000000000..1c39d497c16d1 --- /dev/null +++ b/api_docs/kbn_code_editor_mocks.devdocs.json @@ -0,0 +1,547 @@ +{ + "id": "@kbn/code-editor-mocks", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock", + "type": "Class", + "tags": [], + "label": "CodeEditorStorybookMock", + "description": [ + "\nStorybook mock for the `CodeEditor` component" + ], + "signature": [ + { + "pluginId": "@kbn/code-editor-mocks", + "scope": "common", + "docId": "kibKbnCodeEditorMocksPluginApi", + "section": "def-common.CodeEditorStorybookMock", + "text": "CodeEditorStorybookMock" + }, + " extends ", + { + "pluginId": "@kbn/shared-ux-storybook-mock", + "scope": "common", + "docId": "kibKbnSharedUxStorybookMockPluginApi", + "section": "def-common.AbstractStorybookMock", + "text": "AbstractStorybookMock" + }, + "<", + "Props", + ", {}, PropArguments, {}>" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments", + "type": "Object", + "tags": [], + "label": "propArguments", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.languageId", + "type": "Object", + "tags": [], + "label": "languageId", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.languageId.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.languageId.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.languageId.options", + "type": "Array", + "tags": [], + "label": "options", + "description": [], + "signature": [ + "string[]" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.languageId.defaultValue", + "type": "string", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.value", + "type": "Object", + "tags": [], + "label": "value", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.value.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.value.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.value.defaultValue", + "type": "string", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.arialabel", + "type": "Object", + "tags": [], + "label": "'aria-label'", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.arialabel.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.arialabel.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.arialabel.defaultValue", + "type": "string", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.allowFullScreen", + "type": "Object", + "tags": [], + "label": "allowFullScreen", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.allowFullScreen.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.allowFullScreen.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.allowFullScreen.defaultValue", + "type": "boolean", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.useDarkTheme", + "type": "Object", + "tags": [], + "label": "useDarkTheme", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.useDarkTheme.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.useDarkTheme.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.useDarkTheme.defaultValue", + "type": "boolean", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.transparentBackground", + "type": "Object", + "tags": [], + "label": "transparentBackground", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.transparentBackground.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.transparentBackground.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.transparentBackground.defaultValue", + "type": "boolean", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.placeholder", + "type": "Object", + "tags": [], + "label": "placeholder", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.placeholder.control", + "type": "Object", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.placeholder.control.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.propArguments.placeholder.defaultValue", + "type": "string", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.serviceArguments", + "type": "Object", + "tags": [], + "label": "serviceArguments", + "description": [], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.dependencies", + "type": "Array", + "tags": [], + "label": "dependencies", + "description": [], + "signature": [ + "never[]" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.getProps", + "type": "Function", + "tags": [], + "label": "getProps", + "description": [], + "signature": [ + "(params?: ", + { + "pluginId": "@kbn/code-editor-mocks", + "scope": "common", + "docId": "kibKbnCodeEditorMocksPluginApi", + "section": "def-common.Params", + "text": "Params" + }, + " | undefined) => ", + "Props" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.getProps.$1", + "type": "Object", + "tags": [], + "label": "params", + "description": [], + "signature": [ + { + "pluginId": "@kbn/code-editor-mocks", + "scope": "common", + "docId": "kibKbnCodeEditorMocksPluginApi", + "section": "def-common.Params", + "text": "Params" + }, + " | undefined" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": false + } + ], + "returnComment": [] + }, + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.CodeEditorStorybookMock.getServices", + "type": "Function", + "tags": [], + "label": "getServices", + "description": [], + "signature": [ + "() => { width?: string | number | undefined; height?: string | number | undefined; languageId: enum; value: string; onChange?: ((value: string, event: ", + "editor", + ".IModelContentChangedEvent) => void) | undefined; options?: ", + "editor", + ".IStandaloneEditorConstructionOptions | undefined; suggestionProvider?: ", + "languages", + ".CompletionItemProvider | undefined; signatureProvider?: ", + "languages", + ".SignatureHelpProvider | undefined; hoverProvider?: ", + "languages", + ".HoverProvider | undefined; languageConfiguration?: ", + "languages", + ".LanguageConfiguration | undefined; editorWillMount?: (() => void) | undefined; overrideEditorWillMount?: (() => void) | undefined; editorDidMount?: ((editor: ", + "editor", + ".IStandaloneCodeEditor) => void) | undefined; useDarkTheme?: boolean | undefined; transparentBackground?: boolean | undefined; fullWidth?: boolean | undefined; placeholder?: string | undefined; 'aria-label'?: string | undefined; isCopyable?: boolean | undefined; allowFullScreen?: boolean | undefined; }" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + } + ], + "initialIsOpen": false + } + ], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [ + { + "parentPluginId": "@kbn/code-editor-mocks", + "id": "def-common.Params", + "type": "Type", + "tags": [], + "label": "Params", + "description": [], + "signature": [ + "{ value: any; placeholder: any; \"aria-label\": any; allowFullScreen: any; languageId: any; useDarkTheme: any; transparentBackground: any; }" + ], + "path": "packages/shared-ux/code_editor/mocks/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + } + ], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_code_editor_mocks.mdx b/api_docs/kbn_code_editor_mocks.mdx new file mode 100644 index 0000000000000..e2804c89ba35f --- /dev/null +++ b/api_docs/kbn_code_editor_mocks.mdx @@ -0,0 +1,33 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCodeEditorMocksPluginApi +slug: /kibana-dev-docs/api/kbn-code-editor-mocks +title: "@kbn/code-editor-mocks" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/code-editor-mocks plugin +date: 2023-02-02 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/code-editor-mocks'] +--- +import kbnCodeEditorMocksObj from './kbn_code_editor_mocks.devdocs.json'; + + + +Contact [Owner missing] for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 37 | 0 | 36 | 0 | + +## Common + +### Classes + + +### Consts, variables and types + + diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index 069f23a0c6871..049917bd83438 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index 444b276790315..2d17452f923a1 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 4f30ccfb5a5ea..25b4b7ebacb53 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index e1cf4c7c81a86..d1e0d72178225 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_content_editor.mdx b/api_docs/kbn_content_management_content_editor.mdx index 90acfb8d3c42d..3cc368eec1b45 100644 --- a/api_docs/kbn_content_management_content_editor.mdx +++ b/api_docs/kbn_content_management_content_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-content-editor title: "@kbn/content-management-content-editor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-content-editor plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-content-editor'] --- import kbnContentManagementContentEditorObj from './kbn_content_management_content_editor.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 989bfddc5ad4c..5ce5ade264cd0 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 9207788b71dcb..b663cf574c948 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 10ff4b00e813d..855e9ee974252 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 0a3ad2de810c4..83df4d8d5a2ee 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index 5e25155fe99b3..aa02e316636f7 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index c735376e9bb3d..0b3d418bf4b00 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index fca2385c43a2f..181753d1686f1 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 185c8a8c0d5da..abf9c2389564a 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index eca95e4f9bb4d..e344fd23aedc6 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index e3685fc2066d8..b7392808186a4 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 65fd987d8573e..120bde7908a52 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 18e2f252c0c85..fa360f7b3bb2f 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 6964da198d9a6..7e170abfd7525 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_apps_server_internal.mdx b/api_docs/kbn_core_apps_server_internal.mdx index 841808f6c5d2d..5488ce1d2e961 100644 --- a/api_docs/kbn_core_apps_server_internal.mdx +++ b/api_docs/kbn_core_apps_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-server-internal title: "@kbn/core-apps-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-server-internal'] --- import kbnCoreAppsServerInternalObj from './kbn_core_apps_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index c7c6e08deedc7..b05783625281e 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 53833a277ff3b..7bb91092d7b54 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 4626b08cc0781..55eb1cc972e83 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index 4e578e84321dd..533515b954f12 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 69bbcc7424ca2..9116f0baaa3e8 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index 9f9920fbfcb44..3105448b0f9ca 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index 18f8e597892b9..d068c40608ab4 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 22282d38e237a..b22f6eba8144e 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 6851754193ed1..071caddde22d6 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index 4603c1b5db2c0..bbba9eb95f497 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index d66ae746b8dd1..c6e3906d3816d 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser.mdx b/api_docs/kbn_core_custom_branding_browser.mdx index 4607944fe34f2..3ecd465e22838 100644 --- a/api_docs/kbn_core_custom_branding_browser.mdx +++ b/api_docs/kbn_core_custom_branding_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser title: "@kbn/core-custom-branding-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser'] --- import kbnCoreCustomBrandingBrowserObj from './kbn_core_custom_branding_browser.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_internal.mdx b/api_docs/kbn_core_custom_branding_browser_internal.mdx index d36082c846d19..bcacd36e5963a 100644 --- a/api_docs/kbn_core_custom_branding_browser_internal.mdx +++ b/api_docs/kbn_core_custom_branding_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-internal title: "@kbn/core-custom-branding-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-internal'] --- import kbnCoreCustomBrandingBrowserInternalObj from './kbn_core_custom_branding_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_browser_mocks.mdx b/api_docs/kbn_core_custom_branding_browser_mocks.mdx index 227fe7e79e6b0..1ff4fb95c5df3 100644 --- a/api_docs/kbn_core_custom_branding_browser_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-browser-mocks title: "@kbn/core-custom-branding-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-browser-mocks'] --- import kbnCoreCustomBrandingBrowserMocksObj from './kbn_core_custom_branding_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_common.mdx b/api_docs/kbn_core_custom_branding_common.mdx index e8525bba73e6f..348badb266232 100644 --- a/api_docs/kbn_core_custom_branding_common.mdx +++ b/api_docs/kbn_core_custom_branding_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-common title: "@kbn/core-custom-branding-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-common'] --- import kbnCoreCustomBrandingCommonObj from './kbn_core_custom_branding_common.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server.devdocs.json b/api_docs/kbn_core_custom_branding_server.devdocs.json index bf777d2385605..ed18f14cb7ff5 100644 --- a/api_docs/kbn_core_custom_branding_server.devdocs.json +++ b/api_docs/kbn_core_custom_branding_server.devdocs.json @@ -76,6 +76,88 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "@kbn/core-custom-branding-server", + "id": "def-common.CustomBrandingSetup.getBrandingFor", + "type": "Function", + "tags": [], + "label": "getBrandingFor", + "description": [], + "signature": [ + "(request: ", + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + ", options: { unauthenticated?: boolean | undefined; }) => Promise<", + { + "pluginId": "@kbn/core-custom-branding-common", + "scope": "common", + "docId": "kibKbnCoreCustomBrandingCommonPluginApi", + "section": "def-common.CustomBranding", + "text": "CustomBranding" + }, + ">" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-custom-branding-server", + "id": "def-common.CustomBrandingSetup.getBrandingFor.$1", + "type": "Object", + "tags": [], + "label": "request", + "description": [], + "signature": [ + { + "pluginId": "@kbn/core-http-server", + "scope": "common", + "docId": "kibKbnCoreHttpServerPluginApi", + "section": "def-common.KibanaRequest", + "text": "KibanaRequest" + }, + "" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/core-custom-branding-server", + "id": "def-common.CustomBrandingSetup.getBrandingFor.$2", + "type": "Object", + "tags": [], + "label": "options", + "description": [], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-custom-branding-server", + "id": "def-common.CustomBrandingSetup.getBrandingFor.$2.unauthenticated", + "type": "CompoundType", + "tags": [], + "label": "unauthenticated", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ], + "returnComment": [] } ], "initialIsOpen": false @@ -112,7 +194,7 @@ "section": "def-common.KibanaRequest", "text": "KibanaRequest" }, - ") => ", + ", unauthenticated: boolean) => ", { "pluginId": "@kbn/utility-types", "scope": "common", @@ -155,6 +237,17 @@ "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "@kbn/core-custom-branding-server", + "id": "def-common.CustomBrandingFetchFn.$2", + "type": "boolean", + "tags": [], + "label": "unauthenticated", + "description": [], + "path": "packages/core/custom-branding/core-custom-branding-server/types.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_custom_branding_server.mdx b/api_docs/kbn_core_custom_branding_server.mdx index f191340be9c13..39269a898fd3e 100644 --- a/api_docs/kbn_core_custom_branding_server.mdx +++ b/api_docs/kbn_core_custom_branding_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server title: "@kbn/core-custom-branding-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server'] --- import kbnCoreCustomBrandingServerObj from './kbn_core_custom_branding_server.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 6 | 0 | 6 | 0 | +| 11 | 0 | 11 | 0 | ## Common diff --git a/api_docs/kbn_core_custom_branding_server_internal.mdx b/api_docs/kbn_core_custom_branding_server_internal.mdx index b7c7ce11ed2c2..6e69528f61dad 100644 --- a/api_docs/kbn_core_custom_branding_server_internal.mdx +++ b/api_docs/kbn_core_custom_branding_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-internal title: "@kbn/core-custom-branding-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-internal'] --- import kbnCoreCustomBrandingServerInternalObj from './kbn_core_custom_branding_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_custom_branding_server_mocks.mdx b/api_docs/kbn_core_custom_branding_server_mocks.mdx index bcf53c6b63efc..417766aa34ff0 100644 --- a/api_docs/kbn_core_custom_branding_server_mocks.mdx +++ b/api_docs/kbn_core_custom_branding_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-custom-branding-server-mocks title: "@kbn/core-custom-branding-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-custom-branding-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-custom-branding-server-mocks'] --- import kbnCoreCustomBrandingServerMocksObj from './kbn_core_custom_branding_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index a969e55e05969..9d3e35898c078 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index 74d1f269f1bc5..34560e4104d90 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index af85ababf4a9f..5ba41b82b4d13 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index f5f50113bb830..908bf64b5c01e 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 20535709f9bf4..696330c233e99 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index e6f46741b98db..3f07a5fec9aca 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index 411104ede9491..b6a68b76e926f 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index 2e0f829c6f5d4..b39f60c3c5782 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index d046b63b65748..f52c31de801b0 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index db61e55d08c73..ac415407487e0 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index e0b8cf8c6275d..e3f2e6edb9505 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index f81a4b1dca0b2..f289d1dec6bcf 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index 878429f1f432c..77e6d192bc554 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index b74d61fd48bc0..2175f5f7a3320 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index a0f6c751f1322..8fe117bc12329 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 675d43d95945c..7473612873eb4 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 3468fa27b7967..641154f79ab23 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 9f58849fedb91..d3add23605e96 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 2f5d1108069ed..7d497d9109117 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index ddbdafb1c895b..5dd6425e5ef3e 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 8e317783178fe..3d0605f229543 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index e18968f71d09c..11c6a6b5928bc 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 3bbc3dcb1ad3f..279b9a53242e9 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 7bc1f355f3c50..543e28a53ca03 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 1412f197a1178..27dca641052a7 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 2f6dde2fb228d..4483f5a225cc5 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index acd6f5098cc1b..82ef18604e62f 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index c27ee0968e3c6..c191d5588429a 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index 0228a0b7f398c..00267d77d92be 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index cb8eb78565089..200fca84933ea 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index e807dd4b2bf2e..013e1690c3671 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index 4a3e1bb95abf0..c3df4b7c6ed8d 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index ed4d689190141..5ba52aab8a73e 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server.mdx b/api_docs/kbn_core_http_resources_server.mdx index 66cbcfc913e75..11e0ca720e811 100644 --- a/api_docs/kbn_core_http_resources_server.mdx +++ b/api_docs/kbn_core_http_resources_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server title: "@kbn/core-http-resources-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server'] --- import kbnCoreHttpResourcesServerObj from './kbn_core_http_resources_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_internal.mdx b/api_docs/kbn_core_http_resources_server_internal.mdx index 16f80c2ef88c9..3eb99f97a12bc 100644 --- a/api_docs/kbn_core_http_resources_server_internal.mdx +++ b/api_docs/kbn_core_http_resources_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-internal title: "@kbn/core-http-resources-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-internal'] --- import kbnCoreHttpResourcesServerInternalObj from './kbn_core_http_resources_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_resources_server_mocks.mdx b/api_docs/kbn_core_http_resources_server_mocks.mdx index c76203c29fdd9..12a97f6d6e7fe 100644 --- a/api_docs/kbn_core_http_resources_server_mocks.mdx +++ b/api_docs/kbn_core_http_resources_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-resources-server-mocks title: "@kbn/core-http-resources-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-resources-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-resources-server-mocks'] --- import kbnCoreHttpResourcesServerMocksObj from './kbn_core_http_resources_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index de8f7fb32b44d..6f23c3acaf59a 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index eaacfeda3d0dc..22ac14b1afef9 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 3520dc1324a6e..0652a13fe44ad 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index 6b7d2bcd26a8a..4bc7632c77e86 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index 2d88ab176bdcf..c038022919e36 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index d6147f89f809e..5b6e71ec7c156 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 799798ef3955b..b69d20b56b4ae 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index 2061333d751ba..ec23b942a1afe 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 0fdb792899e32..8eb9c8ab99371 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index 84d4904227011..a9692cbea8ccb 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index 742add69ed5a7..7e4d0bf23a837 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,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 description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 1f47663a9540e..d55d689f8b80d 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 1dc629cf9d2e0..1484fd3a35f9b 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.devdocs.json b/api_docs/kbn_core_lifecycle_browser.devdocs.json index 23c3e5ea7bcb4..9b4320221010f 100644 --- a/api_docs/kbn_core_lifecycle_browser.devdocs.json +++ b/api_docs/kbn_core_lifecycle_browser.devdocs.json @@ -803,27 +803,27 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/global_params/delete_param.tsx" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_selector/use_recently_viewed_monitors.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_locations_api.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" }, { "plugin": "synthetics", diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 859261d83c679..c28d44c6db76e 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index e7b57a81de800..666e90e22149d 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server.mdx b/api_docs/kbn_core_lifecycle_server.mdx index ac5ebf3e71e50..21cd7c5520674 100644 --- a/api_docs/kbn_core_lifecycle_server.mdx +++ b/api_docs/kbn_core_lifecycle_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server title: "@kbn/core-lifecycle-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server'] --- import kbnCoreLifecycleServerObj from './kbn_core_lifecycle_server.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_server_mocks.mdx b/api_docs/kbn_core_lifecycle_server_mocks.mdx index a3fed21b9979b..c3151964e6f3a 100644 --- a/api_docs/kbn_core_lifecycle_server_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-server-mocks title: "@kbn/core-lifecycle-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-server-mocks'] --- import kbnCoreLifecycleServerMocksObj from './kbn_core_lifecycle_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_browser_mocks.mdx b/api_docs/kbn_core_logging_browser_mocks.mdx index 52ef44431a4d5..b630ad85405d6 100644 --- a/api_docs/kbn_core_logging_browser_mocks.mdx +++ b/api_docs/kbn_core_logging_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-browser-mocks title: "@kbn/core-logging-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-browser-mocks'] --- import kbnCoreLoggingBrowserMocksObj from './kbn_core_logging_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_common_internal.mdx b/api_docs/kbn_core_logging_common_internal.mdx index 23624669a3f8f..fa1ab058c1719 100644 --- a/api_docs/kbn_core_logging_common_internal.mdx +++ b/api_docs/kbn_core_logging_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-common-internal title: "@kbn/core-logging-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-common-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-common-internal'] --- import kbnCoreLoggingCommonInternalObj from './kbn_core_logging_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 2449965e82474..e569f93915525 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 65ffa182d4220..a5a14834e0d38 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index a0df57e09c979..dc3f576ac27f1 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index c67dd8b1dd1ca..ad1cbfbbde4bd 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index c8ef726cdeb7d..1a4c7d8d067f6 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 832de383b526e..7f0e8567d5156 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 6ffc31d1fca27..b02f1e59dc749 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 4596f78a805ca..0223092c7ec44 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 069fcffdcb454..32eb3da870095 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index a6bd44f60b7c8..9ca427c60b3c6 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 3b1e9026a48b9..2921e4faadde9 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index fc77245dc3cf3..cbdaccbe466ea 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index 4600e9d212c84..ce7ca986d2480 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index 49d93560e3079..fac04fd7b70cf 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index fb4b14a8a8542..7d1dd62f948f2 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 3fdf81609d4d2..f6e30a70dbf11 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 5c6dbc9a9ccb9..0bfaae8e018f3 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index 008e70efbdf43..f1190eed38362 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index f76760c79e8c4..82f9bb2aedf8b 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index 7fa45105d4416..a279de8f2d709 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server.mdx b/api_docs/kbn_core_plugins_server.mdx index 2fa60c1304427..0e1c587829e15 100644 --- a/api_docs/kbn_core_plugins_server.mdx +++ b/api_docs/kbn_core_plugins_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server title: "@kbn/core-plugins-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server'] --- import kbnCorePluginsServerObj from './kbn_core_plugins_server.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_server_mocks.mdx b/api_docs/kbn_core_plugins_server_mocks.mdx index 6ab1c7d6d41e2..0979cf54ee54b 100644 --- a/api_docs/kbn_core_plugins_server_mocks.mdx +++ b/api_docs/kbn_core_plugins_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-server-mocks title: "@kbn/core-plugins-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-server-mocks'] --- import kbnCorePluginsServerMocksObj from './kbn_core_plugins_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index 915d1bee31595..af174f7cc61c5 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 9196688eec7e7..1015d8d299f9e 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 300ff8710b6d6..333810c192d5d 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx index f8a1aeaed572f..7a0672c472a61 100644 --- a/api_docs/kbn_core_rendering_server_internal.mdx +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal title: "@kbn/core-rendering-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] --- import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx index b3dec96995eec..258159bae1f9e 100644 --- a/api_docs/kbn_core_rendering_server_mocks.mdx +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks title: "@kbn/core-rendering-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] --- import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_root_server_internal.mdx b/api_docs/kbn_core_root_server_internal.mdx index 50e74752502a2..89a5d5c0d463f 100644 --- a/api_docs/kbn_core_root_server_internal.mdx +++ b/api_docs/kbn_core_root_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-root-server-internal title: "@kbn/core-root-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-root-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-root-server-internal'] --- import kbnCoreRootServerInternalObj from './kbn_core_root_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json index 27cd4d841d19d..8041e19b0bf5a 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.devdocs.json +++ b/api_docs/kbn_core_saved_objects_api_browser.devdocs.json @@ -2305,15 +2305,15 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/settings/private_locations/hooks/use_location_monitors.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/hooks/use_monitor_name.ts" }, { "plugin": "graph", @@ -2333,11 +2333,11 @@ }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts" }, { "plugin": "synthetics", - "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts" + "path": "x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts" }, { "plugin": "savedObjects", diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index 387bf244f3e84..eb7048050917f 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 7b51ae2386e4f..a0e8b9873de71 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 732c0f388c492..344ddee09febd 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 4dbed3adb50a8..da353638f970c 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 168a6de3884bf..e1fa8d28c2d2f 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 5ed0f53c38540..f87fae7657223 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 3d3d8c7372407..08db71c3269da 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 66402b1e33977..728397bdc6fd3 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index 05461ace74602..d9caf4392e97e 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.devdocs.json b/api_docs/kbn_core_saved_objects_common.devdocs.json index 0b1701f00d67d..35b92637b9190 100644 --- a/api_docs/kbn_core_saved_objects_common.devdocs.json +++ b/api_docs/kbn_core_saved_objects_common.devdocs.json @@ -1564,6 +1564,14 @@ "plugin": "cases", "path": "x-pack/plugins/cases/server/services/so_references.ts" }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/server/services/so_references.ts" + }, + { + "plugin": "cases", + "path": "x-pack/plugins/cases/server/services/so_references.ts" + }, { "plugin": "lists", "path": "x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts" @@ -3289,6 +3297,18 @@ "plugin": "lens", "path": "x-pack/plugins/lens/public/app_plugin/share_action.ts" }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.ts" + }, { "plugin": "infra", "path": "x-pack/plugins/infra/public/common/visualizations/lens/types.ts" @@ -3360,6 +3380,14 @@ { "plugin": "cases", "path": "x-pack/plugins/cases/server/services/user_actions/test_utils.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.test.ts" + }, + { + "plugin": "infra", + "path": "x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_references_manager.test.ts" } ], "initialIsOpen": false diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index 12c46e4a55d1f..c4a5e47318655 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 2df56466d7b39..e85baecb9edd0 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 3e322719e6c3f..8198192ad5acc 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 0c78327852c14..37c68e17d21fe 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index e7c2e6d106e8b..07e789981e008 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 4fccae05fcfb9..a293f35a1d35f 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index 3968ab5e944ea..b35687b6eacb4 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index 5c8209b1dd160..2bd5c250bb9d5 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index a28d111a550b2..b15d0744a8b8e 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 2bab6b5ae9535..d8036e7c4c24f 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index 357fa83730435..6e928cb172f51 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 8dc92118c079f..bfaf4062eb9ab 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index f8c6c61f848cc..3a4f9d0cf0cdc 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 8f5a6fbadf09b..03a2bbf68f7ab 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index 390f909ce2513..5b15259120a93 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index ca0f19aa71b3a..3577d96881301 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_kbn_server.mdx b/api_docs/kbn_core_test_helpers_kbn_server.mdx index de94f700b713f..c0576c4339907 100644 --- a/api_docs/kbn_core_test_helpers_kbn_server.mdx +++ b/api_docs/kbn_core_test_helpers_kbn_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-kbn-server title: "@kbn/core-test-helpers-kbn-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-kbn-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-kbn-server'] --- import kbnCoreTestHelpersKbnServerObj from './kbn_core_test_helpers_kbn_server.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx index 815aa3601ead0..50190156c7058 100644 --- a/api_docs/kbn_core_test_helpers_so_type_serializer.mdx +++ b/api_docs/kbn_core_test_helpers_so_type_serializer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-so-type-serializer title: "@kbn/core-test-helpers-so-type-serializer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-so-type-serializer plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-so-type-serializer'] --- import kbnCoreTestHelpersSoTypeSerializerObj from './kbn_core_test_helpers_so_type_serializer.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_test_utils.mdx b/api_docs/kbn_core_test_helpers_test_utils.mdx index 3c2f405761f69..90c113785165e 100644 --- a/api_docs/kbn_core_test_helpers_test_utils.mdx +++ b/api_docs/kbn_core_test_helpers_test_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-test-utils title: "@kbn/core-test-helpers-test-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-test-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-test-utils'] --- import kbnCoreTestHelpersTestUtilsObj from './kbn_core_test_helpers_test_utils.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index df472a1696b82..5af18c30b5165 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index eee3789145151..450a67675ef76 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index 105503f3e6a6c..7b48153762dd1 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index 4457d3c3e80d2..7d9dbe4a8c2dc 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index e621db85bb8e1..4170c1807a6b2 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 35c0617de2b6f..7d6e4a161484c 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index dc1710d40a927..4c7fa51e015de 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index 50d479a704290..f27c96b032a97 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 9b4f92f88656a..fd8ce3a327280 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index 655d36907edc1..e06160c1924e1 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 721850f712396..5023dac6eb8d6 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index d94d8f39d44d2..450bc0448b88a 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index fe32fe6d09742..6ab0e2efd550a 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index f6f73955e945d..9a50441146032 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index aa57bcafa5d95..7e7bd30b27af4 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_cypress_config.mdx b/api_docs/kbn_cypress_config.mdx index 31aa95ab485f3..06e1080a1a560 100644 --- a/api_docs/kbn_cypress_config.mdx +++ b/api_docs/kbn_cypress_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cypress-config title: "@kbn/cypress-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cypress-config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cypress-config'] --- import kbnCypressConfigObj from './kbn_cypress_config.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 5c7513c4776ef..56dc4c98673cf 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index 20efd16d9c4d4..f91f3e5085e9a 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 93a3bdf8fb699..08c3c994635fa 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index d2574656ee11c..ad333efc510c7 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index b027188acb6d6..e6d02631496c7 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index 02ef2e3f1f149..95bd54c7055c2 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -840,7 +840,7 @@ "label": "fleet", "description": [], "signature": [ - "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly settings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; }" + "{ readonly beatsAgentComparison: string; readonly guide: string; readonly fleetServer: string; readonly fleetServerAddFleetServer: string; readonly settings: string; readonly settingsFleetServerHostSettings: string; readonly settingsFleetServerProxySettings: string; readonly troubleshooting: string; readonly elasticAgent: string; readonly datastreams: string; readonly datastreamsILM: string; readonly datastreamsNamingScheme: string; readonly datastreamsManualRollover: string; readonly installElasticAgent: string; readonly installElasticAgentStandalone: string; readonly packageSignatures: string; readonly upgradeElasticAgent: string; readonly learnMoreBlog: string; readonly apiKeysLearnMore: string; readonly onPremRegistry: string; readonly secureLogstash: string; readonly agentPolicy: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index a9529773dfc77..9e6fa400e8eb1 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index d809cce3b6274..55b80d6aa632e 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index f6524ce9c0971..ca0b2d1b6773f 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_ecs.mdx b/api_docs/kbn_ecs.mdx index 84af0a7b98494..30d9309e38692 100644 --- a/api_docs/kbn_ecs.mdx +++ b/api_docs/kbn_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ecs title: "@kbn/ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ecs plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ecs'] --- import kbnEcsObj from './kbn_ecs.devdocs.json'; diff --git a/api_docs/kbn_es.mdx b/api_docs/kbn_es.mdx index 360f4b68d6d87..54ccaac62c880 100644 --- a/api_docs/kbn_es.mdx +++ b/api_docs/kbn_es.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es title: "@kbn/es" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es'] --- import kbnEsObj from './kbn_es.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 714d4f044fe8c..1507c9921a93a 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index 3c3a74d96af97..4a5ea93c6655f 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.devdocs.json b/api_docs/kbn_es_query.devdocs.json index 4804bbcd473dc..8bfb4b0ebbbd0 100644 --- a/api_docs/kbn_es_query.devdocs.json +++ b/api_docs/kbn_es_query.devdocs.json @@ -1205,7 +1205,9 @@ "section": "def-common.FilterMeta", "text": "FilterMeta" }, - ") => { query: (Record & { query_string?: { query: string; fields?: string[] | undefined; } | undefined; }) | undefined; meta: { alias: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index: string; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; }" + ") => { query: (Record & { query_string?: { query: string; fields?: string[] | undefined; } | undefined; }) | undefined; meta: { alias: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index: string; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: ", + "FilterMetaParams", + " | undefined; value?: string | undefined; }; }" ], "path": "packages/kbn-es-query/src/filters/build_filters/query_string_filter.ts", "deprecated": false, @@ -3393,7 +3395,7 @@ "label": "isRangeFilter", "description": [], "signature": [ - "(filter?: ", + "(filter: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -3401,14 +3403,7 @@ "section": "def-common.Filter", "text": "Filter" }, - " | undefined) => filter is ", - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.RangeFilter", - "text": "RangeFilter" - } + " | undefined) => boolean" ], "path": "packages/kbn-es-query/src/filters/build_filters/range_filter.ts", "deprecated": false, @@ -3840,7 +3835,9 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => { meta: { disabled: boolean; alias?: string | null | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: { store: ", + ") => { meta: { disabled: boolean; alias?: string | null | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: ", + "FilterMetaParams", + " | undefined; value?: string | undefined; }; $state?: { store: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -3899,7 +3896,9 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => { meta: { negate: boolean; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }; $state?: { store: ", + ") => { meta: { negate: boolean; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: ", + "FilterMetaParams", + " | undefined; value?: string | undefined; }; $state?: { store: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -3958,66 +3957,831 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => { $state: { store: ", + ") => { $state: { store: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterStateStore", + "text": "FilterStateStore" + }, + "; }; meta: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + "; query?: Record | undefined; }" + ], + "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.toggleFilterPinned.$1", + "type": "Object", + "tags": [], + "label": "filter", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + } + ], + "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "A copy of the filter with a toggled pinned state (toggles store from app to global and vice versa)" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.uniqFilters", + "type": "Function", + "tags": [], + "label": "uniqFilters", + "description": [ + "\nRemove duplicate filters from an array of filters\n" + ], + "signature": [ + "(filters: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[], comparatorOptions?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterCompareOptions", + "text": "FilterCompareOptions" + }, + ") => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "packages/kbn-es-query/src/filters/helpers/uniq_filters.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.uniqFilters.$1", + "type": "Array", + "tags": [], + "label": "filters", + "description": [ + "The filters to remove duplicates from" + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]" + ], + "path": "packages/kbn-es-query/src/filters/helpers/uniq_filters.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.uniqFilters.$2", + "type": "Object", + "tags": [], + "label": "comparatorOptions", + "description": [ + "- Parameters to use for comparison" + ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterCompareOptions", + "text": "FilterCompareOptions" + } + ], + "path": "packages/kbn-es-query/src/filters/helpers/uniq_filters.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "The original filters array with duplicates removed" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.unpinFilter", + "type": "Function", + "tags": [], + "label": "unpinFilter", + "description": [], + "signature": [ + "(filter: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ") => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + } + ], + "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.unpinFilter.$1", + "type": "Object", + "tags": [], + "label": "filter", + "description": [], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + } + ], + "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [ + "An unpinned (app scoped) copy of the filter" + ], + "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/es-query", + "id": "def-common.updateFilter", + "type": "Function", + "tags": [], + "label": "updateFilter", + "description": [], + "signature": [ + "(filter: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", field?: string | undefined, operator?: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + " | undefined, params?: ", + "FilterMetaParams", + " | undefined, fieldType?: string | undefined) => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | { meta: { key: string | undefined; field: string | undefined; params: { query: undefined; }; value: undefined; type: undefined; alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; }; query: undefined; $state?: { store: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterStateStore", + "text": "FilterStateStore" + }, + "; } | undefined; } | { meta: { negate: boolean | undefined; type: string | undefined; params: { gte: any; lt: any; }; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; key?: string | undefined; value?: string | undefined; }; query: { range: { [x: string]: { gte: any; lt: any; }; }; }; $state?: { store: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterStateStore", + "text": "FilterStateStore" + }, + "; } | undefined; } | { meta: { negate: boolean | undefined; type: string | undefined; params: { query: ", + "FilterMetaParams", + " | undefined; length: number; toString(): string; toLocaleString(): string; pop(): string | undefined; push(...items: string[]): number; concat(...items: ConcatArray[]): string[]; concat(...items: (string | ConcatArray)[]): string[]; join(separator?: string | undefined): string; reverse(): string[]; shift(): string | undefined; slice(start?: number | undefined, end?: number | undefined): string[]; sort(compareFn?: ((a: string, b: string) => number) | undefined): string[]; splice(start: number, deleteCount?: number | undefined): string[]; splice(start: number, deleteCount: number, ...items: string[]): string[]; unshift(...items: string[]): number; indexOf(searchElement: string, fromIndex?: number | undefined): number; lastIndexOf(searchElement: string, fromIndex?: number | undefined): number; every(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[]; every(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; some(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean; forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void; map(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any): U[]; filter(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; filter(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; reduce(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; reduce(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; reduce(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; reduceRight(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string; reduceRight(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string; reduceRight(callbackfn: (previousValue: U, currentValue: string, currentIndex: number, array: string[]) => U, initialValue: U): U; find(predicate: (this: void, value: string, index: number, obj: string[]) => value is S, thisArg?: any): S | undefined; find(predicate: (value: string, index: number, obj: string[]) => unknown, thisArg?: any): string | undefined; findIndex(predicate: (value: string, index: number, obj: string[]) => unknown, thisArg?: any): number; fill(value: string, start?: number | undefined, end?: number | undefined): string[]; copyWithin(target: number, start: number, end?: number | undefined): string[]; entries(): IterableIterator<[number, string]>; keys(): IterableIterator; values(): IterableIterator; includes(searchElement: string, fromIndex?: number | undefined): boolean; flatMap(callback: (this: This, value: string, index: number, array: string[]) => U | readonly U[], thisArg?: This | undefined): U[]; flat(this: A, depth?: D | undefined): FlatArray[]; [Symbol.iterator](): IterableIterator; [Symbol.unscopables](): { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; at(index: number): string | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; length: number; toString(): string; toLocaleString(): string; pop(): number | undefined; push(...items: number[]): number; concat(...items: ConcatArray[]): number[]; concat(...items: (number | ConcatArray)[]): number[]; join(separator?: string | undefined): string; reverse(): number[]; shift(): number | undefined; slice(start?: number | undefined, end?: number | undefined): number[]; sort(compareFn?: ((a: number, b: number) => number) | undefined): number[]; splice(start: number, deleteCount?: number | undefined): number[]; splice(start: number, deleteCount: number, ...items: number[]): number[]; unshift(...items: number[]): number; indexOf(searchElement: number, fromIndex?: number | undefined): number; lastIndexOf(searchElement: number, fromIndex?: number | undefined): number; every(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): this is S[]; every(predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; some(predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): boolean; forEach(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any): void; map(callbackfn: (value: number, index: number, array: number[]) => U, thisArg?: any): U[]; filter(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S[]; filter(predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number[]; reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number): number; reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number; reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: number[]) => U, initialValue: U): U; find(predicate: (this: void, value: number, index: number, obj: number[]) => value is S, thisArg?: any): S | undefined; find(predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any): number | undefined; findIndex(predicate: (value: number, index: number, obj: number[]) => unknown, thisArg?: any): number; fill(value: number, start?: number | undefined, end?: number | undefined): number[]; copyWithin(target: number, start: number, end?: number | undefined): number[]; entries(): IterableIterator<[number, number]>; keys(): IterableIterator; values(): IterableIterator; includes(searchElement: number, fromIndex?: number | undefined): boolean; flatMap(callback: (this: This, value: number, index: number, array: number[]) => U | readonly U[], thisArg?: This | undefined): U[]; flat(this: A, depth?: D | undefined): FlatArray[]; [Symbol.iterator](): IterableIterator; [Symbol.unscopables](): { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; at(index: number): number | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; length: number; toString(): string; toLocaleString(): string; pop(): boolean | undefined; push(...items: boolean[]): number; concat(...items: ConcatArray[]): boolean[]; concat(...items: (boolean | ConcatArray)[]): boolean[]; join(separator?: string | undefined): string; reverse(): boolean[]; shift(): boolean | undefined; slice(start?: number | undefined, end?: number | undefined): boolean[]; sort(compareFn?: ((a: boolean, b: boolean) => number) | undefined): boolean[]; splice(start: number, deleteCount?: number | undefined): boolean[]; splice(start: number, deleteCount: number, ...items: boolean[]): boolean[]; unshift(...items: boolean[]): number; indexOf(searchElement: boolean, fromIndex?: number | undefined): number; lastIndexOf(searchElement: boolean, fromIndex?: number | undefined): number; every(predicate: (value: boolean, index: number, array: boolean[]) => value is S, thisArg?: any): this is S[]; every(predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean; some(predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean; forEach(callbackfn: (value: boolean, index: number, array: boolean[]) => void, thisArg?: any): void; map(callbackfn: (value: boolean, index: number, array: boolean[]) => U, thisArg?: any): U[]; filter(predicate: (value: boolean, index: number, array: boolean[]) => value is S, thisArg?: any): S[]; filter(predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean[]; reduce(callbackfn: (previousValue: boolean, currentValue: boolean, currentIndex: number, array: boolean[]) => boolean): boolean; reduce(callbackfn: (previousValue: boolean, currentValue: boolean, currentIndex: number, array: boolean[]) => boolean, initialValue: boolean): boolean; reduce(callbackfn: (previousValue: U, currentValue: boolean, currentIndex: number, array: boolean[]) => U, initialValue: U): U; reduceRight(callbackfn: (previousValue: boolean, currentValue: boolean, currentIndex: number, array: boolean[]) => boolean): boolean; reduceRight(callbackfn: (previousValue: boolean, currentValue: boolean, currentIndex: number, array: boolean[]) => boolean, initialValue: boolean): boolean; reduceRight(callbackfn: (previousValue: U, currentValue: boolean, currentIndex: number, array: boolean[]) => U, initialValue: U): U; find(predicate: (this: void, value: boolean, index: number, obj: boolean[]) => value is S, thisArg?: any): S | undefined; find(predicate: (value: boolean, index: number, obj: boolean[]) => unknown, thisArg?: any): boolean | undefined; findIndex(predicate: (value: boolean, index: number, obj: boolean[]) => unknown, thisArg?: any): number; fill(value: boolean, start?: number | undefined, end?: number | undefined): boolean[]; copyWithin(target: number, start: number, end?: number | undefined): boolean[]; entries(): IterableIterator<[number, boolean]>; keys(): IterableIterator; values(): IterableIterator; includes(searchElement: boolean, fromIndex?: number | undefined): boolean; flatMap(callback: (this: This, value: boolean, index: number, array: boolean[]) => U | readonly U[], thisArg?: This | undefined): U[]; flat(this: A, depth?: D | undefined): FlatArray[]; [Symbol.iterator](): IterableIterator; [Symbol.unscopables](): { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; at(index: number): boolean | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; $state?: { store: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterStateStore", + "text": "FilterStateStore" + }, + "; } | undefined; meta: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + "; } | { query: ", + "FilterMetaParams", + " | undefined; from?: string | number | undefined; to?: string | number | undefined; gt?: string | number | undefined; lt?: string | number | undefined; gte?: string | number | undefined; lte?: string | number | undefined; format?: string | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type: \"range\"; key?: string | undefined; params?: (", + "FilterMetaParams", + " & ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.RangeFilterParams", + "text": "RangeFilterParams" + }, + ") | undefined; value?: string | undefined; field?: string | undefined; formattedValue?: string | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; length: number; toString(): string; toLocaleString(): string; pop(): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | undefined; push(...items: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]): number; concat(...items: ConcatArray<", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ">[]): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; concat(...items: (", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | ConcatArray<", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ">)[]): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; join(separator?: string | undefined): string; reverse(): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; shift(): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + " | undefined; slice(start?: number | undefined, end?: number | undefined): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; sort(compareFn?: ((a: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", b: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ") => number) | undefined): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; splice(start: number, deleteCount?: number | undefined): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; splice(start: number, deleteCount: number, ...items: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; unshift(...items: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]): number; indexOf(searchElement: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", fromIndex?: number | undefined): number; lastIndexOf(searchElement: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", fromIndex?: number | undefined): number; every(predicate: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => value is S, thisArg?: any): this is S[]; every(predicate: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => unknown, thisArg?: any): boolean; some(predicate: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => unknown, thisArg?: any): boolean; forEach(callbackfn: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => void, thisArg?: any): void; map(callbackfn: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => U, thisArg?: any): U[]; filter(predicate: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => value is S, thisArg?: any): S[]; filter(predicate: (value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => unknown, thisArg?: any): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; reduce(callbackfn: (previousValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentIndex: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "; reduce(callbackfn: (previousValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentIndex: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", initialValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "; reduce(callbackfn: (previousValue: U, currentValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentIndex: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => U, initialValue: U): U; reduceRight(callbackfn: (previousValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentIndex: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "; reduceRight(callbackfn: (previousValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentIndex: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", initialValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "): ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "; reduceRight(callbackfn: (previousValue: U, currentValue: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", currentIndex: number, array: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => U, initialValue: U): U; find(predicate: (this: void, value: ", + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ", index: number, obj: ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterStateStore", - "text": "FilterStateStore" + "section": "def-common.Filter", + "text": "Filter" }, - "; }; meta: ", + "[]) => value is S, thisArg?: any): S | undefined; find(predicate: (value: ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterMeta", - "text": "FilterMeta" + "section": "def-common.Filter", + "text": "Filter" }, - "; query?: Record | undefined; }" - ], - "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + ", index: number, obj: ", { - "parentPluginId": "@kbn/es-query", - "id": "def-common.toggleFilterPinned.$1", - "type": "Object", - "tags": [], - "label": "filter", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - } - ], - "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [ - "A copy of the filter with a toggled pinned state (toggles store from app to global and vice versa)" - ], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/es-query", - "id": "def-common.uniqFilters", - "type": "Function", - "tags": [], - "label": "uniqFilters", - "description": [ - "\nRemove duplicate filters from an array of filters\n" - ], - "signature": [ - "(filters: ", + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]) => unknown, thisArg?: any): ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -4025,15 +4789,15 @@ "section": "def-common.Filter", "text": "Filter" }, - "[], comparatorOptions?: ", + " | undefined; findIndex(predicate: (value: ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterCompareOptions", - "text": "FilterCompareOptions" + "section": "def-common.Filter", + "text": "Filter" }, - ") => ", + ", index: number, obj: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -4041,74 +4805,23 @@ "section": "def-common.Filter", "text": "Filter" }, - "[]" - ], - "path": "packages/kbn-es-query/src/filters/helpers/uniq_filters.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + "[]) => unknown, thisArg?: any): number; fill(value: ", { - "parentPluginId": "@kbn/es-query", - "id": "def-common.uniqFilters.$1", - "type": "Array", - "tags": [], - "label": "filters", - "description": [ - "The filters to remove duplicates from" - ], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - }, - "[]" - ], - "path": "packages/kbn-es-query/src/filters/helpers/uniq_filters.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" }, + ", start?: number | undefined, end?: number | undefined): ", { - "parentPluginId": "@kbn/es-query", - "id": "def-common.uniqFilters.$2", - "type": "Object", - "tags": [], - "label": "comparatorOptions", - "description": [ - "- Parameters to use for comparison" - ], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterCompareOptions", - "text": "FilterCompareOptions" - } - ], - "path": "packages/kbn-es-query/src/filters/helpers/uniq_filters.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [ - "The original filters array with duplicates removed" - ], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/es-query", - "id": "def-common.unpinFilter", - "type": "Function", - "tags": [], - "label": "unpinFilter", - "description": [], - "signature": [ - "(filter: ", + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + "[]; copyWithin(target: number, start: number, end?: number | undefined): ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -4116,55 +4829,23 @@ "section": "def-common.Filter", "text": "Filter" }, - ") => ", + "[]; entries(): IterableIterator<[number, ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", "section": "def-common.Filter", "text": "Filter" - } - ], - "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ + }, + "]>; keys(): IterableIterator; values(): IterableIterator<", { - "parentPluginId": "@kbn/es-query", - "id": "def-common.unpinFilter.$1", - "type": "Object", - "tags": [], - "label": "filter", - "description": [], - "signature": [ - { - "pluginId": "@kbn/es-query", - "scope": "common", - "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.Filter", - "text": "Filter" - } - ], - "path": "packages/kbn-es-query/src/filters/helpers/meta_filter.ts", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [ - "An unpinned (app scoped) copy of the filter" - ], - "initialIsOpen": false - }, - { - "parentPluginId": "@kbn/es-query", - "id": "def-common.updateFilter", - "type": "Function", - "tags": [], - "label": "updateFilter", - "description": [], - "signature": [ - "(filter: ", + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.Filter", + "text": "Filter" + }, + ">; includes(searchElement: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -4172,39 +4853,59 @@ "section": "def-common.Filter", "text": "Filter" }, - ", field?: string | undefined, operator?: FilterOperator | undefined, params?: any, fieldType?: string | undefined) => { meta: { key: string | undefined; field: string | undefined; params: { query: undefined; }; value: undefined; type: undefined; alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; }; query: undefined; $state?: { store: ", + ", fromIndex?: number | undefined): boolean; flatMap(callback: (this: This, value: ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterStateStore", - "text": "FilterStateStore" + "section": "def-common.Filter", + "text": "Filter" }, - "; } | undefined; } | { meta: { negate: boolean | undefined; type: string | undefined; params: undefined; value: string; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; key?: string | undefined; }; query: { exists: { field: string | undefined; }; }; $state?: { store: ", + ", index: number, array: ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterStateStore", - "text": "FilterStateStore" + "section": "def-common.Filter", + "text": "Filter" }, - "; } | undefined; } | { meta: { negate: boolean | undefined; type: string | undefined; params: { gte: string | number | undefined; lt: string | number | undefined; }; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; key?: string | undefined; value?: string | undefined; }; query: { range: { [x: string]: { gte: string | number | undefined; lt: string | number | undefined; }; }; }; $state?: { store: ", + "[]) => U | readonly U[], thisArg?: This | undefined): U[]; flat(this: A, depth?: D | undefined): FlatArray[]; [Symbol.iterator](): IterableIterator<", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterStateStore", - "text": "FilterStateStore" + "section": "def-common.Filter", + "text": "Filter" }, - "; } | undefined; } | { meta: { negate: boolean | undefined; type: string | undefined; params: any[] | undefined; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; key?: string | undefined; value?: string | undefined; }; query: { bool: any; }; $state?: { store: ", + ">; [Symbol.unscopables](): { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }; at(index: number): ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.FilterStateStore", - "text": "FilterStateStore" + "section": "def-common.Filter", + "text": "Filter" }, - "; } | undefined; } | { meta: { negate: boolean | undefined; type: string | undefined; params: any; value: undefined; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; key?: string | undefined; }; query: { match_phrase: { [x: string]: any; }; }; $state?: { store: ", + " | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: (", + "FilterMetaParams", + " & ", + "PhraseFilterMetaParams", + ") | undefined; value?: string | undefined; field?: string | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params: (", + "FilterMetaParams", + " | undefined) & ", + "PhraseFilterValue", + "[]; value?: string | undefined; field?: string | undefined; } | { query: ", + "FilterMetaParams", + " | undefined; field: string; formattedValue: string; alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: ", + "FilterMetaParams", + " | undefined; value?: string | undefined; }; value: undefined; alias?: string | null | undefined; disabled?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; key?: string | undefined; }; query: { match_phrase: { [x: string]: ", + "FilterMetaParams", + "; }; }; $state?: { store: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -4262,7 +4963,14 @@ "label": "operator", "description": [], "signature": [ - "FilterOperator | undefined" + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.FilterMeta", + "text": "FilterMeta" + }, + " | undefined" ], "path": "packages/kbn-es-query/src/filters/helpers/update_filter.ts", "deprecated": false, @@ -4272,17 +4980,18 @@ { "parentPluginId": "@kbn/es-query", "id": "def-common.updateFilter.$4", - "type": "Any", + "type": "CompoundType", "tags": [], "label": "params", "description": [], "signature": [ - "any" + "FilterMetaParams", + " | undefined" ], "path": "packages/kbn-es-query/src/filters/helpers/update_filter.ts", "deprecated": false, "trackAdoption": false, - "isRequired": true + "isRequired": false }, { "parentPluginId": "@kbn/es-query", @@ -4831,6 +5540,23 @@ "description": [ "\nAn interface for all possible range filter params\nIt is similar, but not identical to estypes.QueryDslRangeQuery" ], + "signature": [ + { + "pluginId": "@kbn/es-query", + "scope": "common", + "docId": "kibKbnEsQueryPluginApi", + "section": "def-common.RangeFilterParams", + "text": "RangeFilterParams" + }, + " extends ", + { + "pluginId": "@kbn/utility-types", + "scope": "common", + "docId": "kibKbnUtilityTypesPluginApi", + "section": "def-common.SerializableRecord", + "text": "SerializableRecord" + } + ], "path": "packages/kbn-es-query/src/filters/build_filters/range_filter.ts", "deprecated": false, "trackAdoption": false, @@ -5183,24 +5909,24 @@ "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.PhrasesFilter", - "text": "PhrasesFilter" + "section": "def-common.PhraseFilter", + "text": "PhraseFilter" }, " | ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.ExistsFilter", - "text": "ExistsFilter" + "section": "def-common.PhrasesFilter", + "text": "PhrasesFilter" }, " | ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.PhraseFilter", - "text": "PhraseFilter" + "section": "def-common.ExistsFilter", + "text": "ExistsFilter" }, " | ", { @@ -5255,7 +5981,9 @@ "label": "FilterMeta", "description": [], "signature": [ - "{ alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: any; value?: string | undefined; }" + "{ alias?: string | null | undefined; disabled?: boolean | undefined; negate?: boolean | undefined; controlledBy?: string | undefined; group?: string | undefined; index?: string | undefined; isMultiIndex?: boolean | undefined; type?: string | undefined; key?: string | undefined; params?: ", + "FilterMetaParams", + " | undefined; value?: string | undefined; }" ], "path": "packages/kbn-es-query/src/filters/build_filters/types.ts", "deprecated": false, @@ -5459,7 +6187,7 @@ "section": "def-common.FilterMeta", "text": "FilterMeta" }, - " & { params: ", + " & { params?: ", { "pluginId": "@kbn/es-query", "scope": "common", @@ -5467,7 +6195,7 @@ "section": "def-common.RangeFilterParams", "text": "RangeFilterParams" }, - "; field?: string | undefined; formattedValue?: string | undefined; }" + " | undefined; field?: string | undefined; formattedValue?: string | undefined; type: \"range\"; }" ], "path": "packages/kbn-es-query/src/filters/build_filters/range_filter.ts", "deprecated": false, diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index da781ad196acd..8861b6735b9cf 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 250 | 2 | 192 | 13 | +| 250 | 1 | 192 | 15 | ## Common diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 5c495dd649658..1c225b95d6794 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index 7c43007947950..83c40ea9fa494 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index 159dc92930b43..73d840aaea6bb 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index d3c1d8f62910f..b8b1df36612bd 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index b91ea5b281d51..e12319c4cffef 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 413dd63d35735..aa372f6f1f8f7 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 64a9c1e5b7bf2..5da9b8bf77842 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_guided_onboarding.devdocs.json b/api_docs/kbn_guided_onboarding.devdocs.json index 09b27f31eede7..246ef8f89dd34 100644 --- a/api_docs/kbn_guided_onboarding.devdocs.json +++ b/api_docs/kbn_guided_onboarding.devdocs.json @@ -21,31 +21,31 @@ "functions": [ { "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.GuideCard", + "id": "def-common.GuideCards", "type": "Function", "tags": [], - "label": "GuideCard", + "label": "GuideCards", "description": [], "signature": [ - "({ useCase, guides, activateGuide, isDarkTheme, addBasePath, }: ", - "GuideCardProps", + "(props: ", + "GuideCardsProps", ") => JSX.Element" ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx", + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.GuideCard.$1", + "id": "def-common.GuideCards.$1", "type": "Object", "tags": [], - "label": "{\n useCase,\n guides,\n activateGuide,\n isDarkTheme,\n addBasePath,\n}", + "label": "props", "description": [], "signature": [ - "GuideCardProps" + "GuideCardsProps" ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx", + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.tsx", "deprecated": false, "trackAdoption": false, "isRequired": true @@ -56,143 +56,32 @@ }, { "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard", + "id": "def-common.GuideFilters", "type": "Function", "tags": [], - "label": "InfrastructureLinkCard", + "label": "GuideFilters", "description": [], "signature": [ - "({ navigateToApp, isDarkTheme, addBasePath, }: { navigateToApp: (appId: string, options?: ", - { - "pluginId": "@kbn/core-application-browser", - "scope": "common", - "docId": "kibKbnCoreApplicationBrowserPluginApi", - "section": "def-common.NavigateToAppOptions", - "text": "NavigateToAppOptions" - }, - " | undefined) => Promise; isDarkTheme: boolean; addBasePath: (url: string) => string; }) => JSX.Element" + "({ activeFilter, setActiveFilter }: GuideFiltersProps) => JSX.Element" ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_filters.tsx", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1", + "id": "def-common.GuideFilters.$1", "type": "Object", "tags": [], - "label": "{\n navigateToApp,\n isDarkTheme,\n addBasePath,\n}", + "label": "{ activeFilter, setActiveFilter }", "description": [], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", + "signature": [ + "GuideFiltersProps" + ], + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_filters.tsx", "deprecated": false, "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1.navigateToApp", - "type": "Function", - "tags": [], - "label": "navigateToApp", - "description": [], - "signature": [ - "(appId: string, options?: ", - { - "pluginId": "@kbn/core-application-browser", - "scope": "common", - "docId": "kibKbnCoreApplicationBrowserPluginApi", - "section": "def-common.NavigateToAppOptions", - "text": "NavigateToAppOptions" - }, - " | undefined) => Promise" - ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1.navigateToApp.$1", - "type": "string", - "tags": [], - "label": "appId", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - }, - { - "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1.navigateToApp.$2", - "type": "Object", - "tags": [], - "label": "options", - "description": [], - "signature": [ - { - "pluginId": "@kbn/core-application-browser", - "scope": "common", - "docId": "kibKbnCoreApplicationBrowserPluginApi", - "section": "def-common.NavigateToAppOptions", - "text": "NavigateToAppOptions" - }, - " | undefined" - ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": false - } - ], - "returnComment": [] - }, - { - "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1.isDarkTheme", - "type": "boolean", - "tags": [], - "label": "isDarkTheme", - "description": [], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", - "deprecated": false, - "trackAdoption": false - }, - { - "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1.addBasePath", - "type": "Function", - "tags": [], - "label": "addBasePath", - "description": [], - "signature": [ - "(url: string) => string" - ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.InfrastructureLinkCard.$1.addBasePath.$1", - "type": "string", - "tags": [], - "label": "url", - "description": [], - "signature": [ - "string" - ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx", - "deprecated": false, - "trackAdoption": false, - "isRequired": true - } - ], - "returnComment": [] - } - ] + "isRequired": true } ], "returnComment": [], @@ -638,15 +527,16 @@ "misc": [ { "parentPluginId": "@kbn/guided-onboarding", - "id": "def-common.GuideCardUseCase", + "id": "def-common.GuideFilterValues", "type": "Type", "tags": [], - "label": "GuideCardUseCase", + "label": "GuideFilterValues", "description": [], "signature": [ - "\"search\" | \"kubernetes\" | \"siem\"" + "\"all\" | ", + "GuideCardSolutions" ], - "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx", + "path": "packages/kbn-guided-onboarding/src/components/landing_page/guide_filters.tsx", "deprecated": false, "trackAdoption": false, "initialIsOpen": false diff --git a/api_docs/kbn_guided_onboarding.mdx b/api_docs/kbn_guided_onboarding.mdx index e078a0ec24285..af01c0d7256f2 100644 --- a/api_docs/kbn_guided_onboarding.mdx +++ b/api_docs/kbn_guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-guided-onboarding title: "@kbn/guided-onboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/guided-onboarding plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/guided-onboarding'] --- import kbnGuidedOnboardingObj from './kbn_guided_onboarding.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 58 | 0 | 56 | 1 | +| 52 | 0 | 50 | 2 | ## Common diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 126aba552fcff..177de30fa7546 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 6adb4eb34c18e..6fa1d6f9f1122 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_health_gateway_server.mdx b/api_docs/kbn_health_gateway_server.mdx index 2e095a65db292..22c0fd0a50667 100644 --- a/api_docs/kbn_health_gateway_server.mdx +++ b/api_docs/kbn_health_gateway_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-health-gateway-server title: "@kbn/health-gateway-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/health-gateway-server plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/health-gateway-server'] --- import kbnHealthGatewayServerObj from './kbn_health_gateway_server.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index 2b295a805e432..9513e554824d4 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index c397501db921f..61f5a3e462a23 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index 2a49529919548..3f5772d15740c 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_i18n_react.mdx b/api_docs/kbn_i18n_react.mdx index 5fcd0953e989d..f09be75044bbe 100644 --- a/api_docs/kbn_i18n_react.mdx +++ b/api_docs/kbn_i18n_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n-react title: "@kbn/i18n-react" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n-react plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n-react'] --- import kbnI18nReactObj from './kbn_i18n_react.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 2c7b594374be4..bc84da7492c2c 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index b578d6f0ff054..39b9d75d35825 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index a5fd9cbf4e7dd..4c98355a61e2c 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index 11853327bf239..79c7d63e68da3 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 11b23f85db0ea..1831a0ea52ab3 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_json_ast.mdx b/api_docs/kbn_json_ast.mdx index 3927f61297815..b825cd4a3c8da 100644 --- a/api_docs/kbn_json_ast.mdx +++ b/api_docs/kbn_json_ast.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-json-ast title: "@kbn/json-ast" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/json-ast plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/json-ast'] --- import kbnJsonAstObj from './kbn_json_ast.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.devdocs.json b/api_docs/kbn_kibana_manifest_schema.devdocs.json index fb46dedb5099d..e9509c172d6e8 100644 --- a/api_docs/kbn_kibana_manifest_schema.devdocs.json +++ b/api_docs/kbn_kibana_manifest_schema.devdocs.json @@ -1034,6 +1034,269 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build", + "type": "Object", + "tags": [], + "label": "build", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties", + "type": "Object", + "tags": [], + "label": "properties", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.extraExcludes", + "type": "Object", + "tags": [], + "label": "extraExcludes", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.extraExcludes.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.extraExcludes.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.extraExcludes.items", + "type": "Object", + "tags": [], + "label": "items", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.extraExcludes.items.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.noParse", + "type": "Object", + "tags": [], + "label": "noParse", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.noParse.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.noParse.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.noParse.items", + "type": "Object", + "tags": [], + "label": "items", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.build.properties.noParse.items.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] + } + ] + } + ] + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.serviceFolders", + "type": "Object", + "tags": [], + "label": "serviceFolders", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.serviceFolders.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.serviceFolders.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.serviceFolders.items", + "type": "Object", + "tags": [], + "label": "items", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.serviceFolders.items.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.serviceFolders.deprecated", + "type": "boolean", + "tags": [], + "label": "deprecated", + "description": [], + "signature": [ + "true" + ], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + } + ] + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.description", + "type": "Object", + "tags": [], + "label": "description", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.description.description", + "type": "string", + "tags": [], + "label": "description", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/kibana-manifest-schema", + "id": "def-common.MANIFEST_V2.properties.description.type", + "type": "string", + "tags": [], + "label": "type", + "description": [], + "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ] }, @@ -1045,7 +1308,7 @@ "label": "oneOf", "description": [], "signature": [ - "({ type: string; properties: { type: { enum: string[]; }; plugin: { type: string; required: string[]; properties: { id: { type: string; pattern: string; }; configPath: { description: string; type: string; items: { type: string; pattern: string; }; }; requiredPlugins: { type: string; items: { type: string; pattern: string; }; }; optionalPlugins: { type: string; items: { type: string; pattern: string; }; }; description: { description: string; type: string; }; enabledOnAnonymousPages: { description: string; type: string; }; serviceFolders: { description: string; type: string; items: { type: string; }; }; }; }; }; } | { type: string; properties: { type: { const: string; }; sharedBrowserBundle: { type: string; description: string; }; }; } | { type: string; properties: { type: { enum: string[]; }; }; })[]" + "({ type: string; required: string[]; properties: { type: { const: string; }; plugin: { type: string; required: string[]; properties: { id: { type: string; pattern: string; }; configPath: { description: string; oneOf: ({ type: string; items: { type: string; }; } | { type: string; })[]; }; requiredPlugins: { type: string; items: { type: string; pattern: string; }; }; optionalPlugins: { type: string; items: { type: string; pattern: string; }; }; enabledOnAnonymousPages: { description: string; type: string; }; type: { description: string; enum: string[]; }; browser: { type: string; description: string; }; server: { type: string; description: string; }; }; }; }; } | { type: string; properties: { type: { const: string; }; sharedBrowserBundle: { type: string; description: string; }; }; } | { type: string; properties: { type: { enum: string[]; }; }; })[]" ], "path": "packages/kbn-kibana-manifest-schema/src/kibana_json_v2_schema.ts", "deprecated": false, diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 3e923c48e785f..1adb8b7f9e840 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 86 | 0 | 85 | 0 | +| 108 | 0 | 107 | 0 | ## Common diff --git a/api_docs/kbn_language_documentation_popover.mdx b/api_docs/kbn_language_documentation_popover.mdx index ec6755a037e54..098537901fd52 100644 --- a/api_docs/kbn_language_documentation_popover.mdx +++ b/api_docs/kbn_language_documentation_popover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-language-documentation-popover title: "@kbn/language-documentation-popover" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/language-documentation-popover plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/language-documentation-popover'] --- import kbnLanguageDocumentationPopoverObj from './kbn_language_documentation_popover.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 2884e1a95e53e..ab0353e48ef49 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 1f5e5a603529a..62c9fe2946366 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index e4fa5b1fc4406..126b1274f5531 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 9073884e3e906..d6b294c8d4cc2 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 50230f0c37c4e..3690e2ce1807e 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_date_picker.mdx b/api_docs/kbn_ml_date_picker.mdx index e0a807ae7fcfc..d4e7c740685a9 100644 --- a/api_docs/kbn_ml_date_picker.mdx +++ b/api_docs/kbn_ml_date_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-date-picker title: "@kbn/ml-date-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-date-picker plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-date-picker'] --- import kbnMlDatePickerObj from './kbn_ml_date_picker.devdocs.json'; diff --git a/api_docs/kbn_ml_is_defined.mdx b/api_docs/kbn_ml_is_defined.mdx index 8663b0973a4fa..43318dd19459b 100644 --- a/api_docs/kbn_ml_is_defined.mdx +++ b/api_docs/kbn_ml_is_defined.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-defined title: "@kbn/ml-is-defined" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-defined plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-defined'] --- import kbnMlIsDefinedObj from './kbn_ml_is_defined.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index bd86f138c7f45..3c32f728ed4e9 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_local_storage.mdx b/api_docs/kbn_ml_local_storage.mdx index e8945cdbae852..3fc42474b9a4c 100644 --- a/api_docs/kbn_ml_local_storage.mdx +++ b/api_docs/kbn_ml_local_storage.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-local-storage title: "@kbn/ml-local-storage" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-local-storage plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-local-storage'] --- import kbnMlLocalStorageObj from './kbn_ml_local_storage.devdocs.json'; diff --git a/api_docs/kbn_ml_nested_property.mdx b/api_docs/kbn_ml_nested_property.mdx index 32b05613d6862..3dcfccff45b6b 100644 --- a/api_docs/kbn_ml_nested_property.mdx +++ b/api_docs/kbn_ml_nested_property.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-nested-property title: "@kbn/ml-nested-property" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-nested-property plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-nested-property'] --- import kbnMlNestedPropertyObj from './kbn_ml_nested_property.devdocs.json'; diff --git a/api_docs/kbn_ml_query_utils.mdx b/api_docs/kbn_ml_query_utils.mdx index 19e67e69be75c..21c6c0055951e 100644 --- a/api_docs/kbn_ml_query_utils.mdx +++ b/api_docs/kbn_ml_query_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-query-utils title: "@kbn/ml-query-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-query-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-query-utils'] --- import kbnMlQueryUtilsObj from './kbn_ml_query_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 726577ba097d1..25a823c6daf76 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_ml_url_state.mdx b/api_docs/kbn_ml_url_state.mdx index a5b520a359366..513656ce6bd12 100644 --- a/api_docs/kbn_ml_url_state.mdx +++ b/api_docs/kbn_ml_url_state.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-url-state title: "@kbn/ml-url-state" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-url-state plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-url-state'] --- import kbnMlUrlStateObj from './kbn_ml_url_state.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index 9b21b62a02005..b5b8f06bff4e4 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index c1c4409f67b83..ef49a2ac71f40 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index b6f86dc0cd4fa..c9d3f3f69f9a0 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 0229ccceff20c..e5c5e416f823f 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 1ac67e0201af6..1467e62c3cbba 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index 182b3d18d1317..e76b91a98c71c 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 4a2f7a1548390..c70133738de2e 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index bcc9621f6eaf5..ec70e3dbe6a85 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_file_maps.mdx b/api_docs/kbn_repo_file_maps.mdx index a8aa63023c828..156bef37af0be 100644 --- a/api_docs/kbn_repo_file_maps.mdx +++ b/api_docs/kbn_repo_file_maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-file-maps title: "@kbn/repo-file-maps" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-file-maps plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-file-maps'] --- import kbnRepoFileMapsObj from './kbn_repo_file_maps.devdocs.json'; diff --git a/api_docs/kbn_repo_linter.mdx b/api_docs/kbn_repo_linter.mdx index 323e39b6192ff..a699292a465fc 100644 --- a/api_docs/kbn_repo_linter.mdx +++ b/api_docs/kbn_repo_linter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-linter title: "@kbn/repo-linter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-linter plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-linter'] --- import kbnRepoLinterObj from './kbn_repo_linter.devdocs.json'; diff --git a/api_docs/kbn_repo_path.mdx b/api_docs/kbn_repo_path.mdx index eae842c432038..c4b121b721edc 100644 --- a/api_docs/kbn_repo_path.mdx +++ b/api_docs/kbn_repo_path.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-path title: "@kbn/repo-path" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-path plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-path'] --- import kbnRepoPathObj from './kbn_repo_path.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 4dd0be8388470..b849e2b7cd41a 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rison.mdx b/api_docs/kbn_rison.mdx index 977aecbf64ae4..11074475cfac1 100644 --- a/api_docs/kbn_rison.mdx +++ b/api_docs/kbn_rison.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rison title: "@kbn/rison" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rison plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rison'] --- import kbnRisonObj from './kbn_rison.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 41036062e5590..1b93a3e0236c8 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -328,6 +328,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/rule-data-utils", + "id": "def-common.ALERT_LAST_DETECTED", + "type": "string", + "tags": [], + "label": "ALERT_LAST_DETECTED", + "description": [], + "signature": [ + "\"kibana.alert.last_detected\"" + ], + "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/rule-data-utils", "id": "def-common.ALERT_NAMESPACE", @@ -1296,7 +1311,7 @@ "label": "DefaultAlertFieldName", "description": [], "signature": [ - "\"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.rule\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"kibana.alert.workflow_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.tags\" | \"kibana.alert.id\"" + "\"kibana\" | \"kibana.alert.rule.rule_type_id\" | \"kibana.alert.rule.consumer\" | \"kibana.alert.rule.execution.uuid\" | \"kibana.alert\" | \"kibana.alert.rule\" | \"kibana.alert.rule.parameters\" | \"kibana.alert.rule.producer\" | \"kibana.space_ids\" | \"kibana.alert.uuid\" | \"kibana.alert.start\" | \"kibana.alert.time_range\" | \"kibana.alert.end\" | \"kibana.alert.duration.us\" | \"kibana.alert.status\" | \"kibana.alert.flapping\" | \"kibana.version\" | \"kibana.alert.workflow_status\" | \"kibana.alert.action_group\" | \"kibana.alert.reason\" | \"kibana.alert.rule.category\" | \"kibana.alert.rule.uuid\" | \"kibana.alert.rule.name\" | \"kibana.alert.rule.tags\" | \"kibana.alert.last_detected\" | \"kibana.alert.id\"" ], "path": "packages/kbn-rule-data-utils/src/default_alerts_as_data.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index aaae88b9fcbb1..38899d6e2dec0 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 99 | 0 | 96 | 0 | +| 100 | 0 | 97 | 0 | ## Common diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index 9d59e07d5c104..b2d6bd3ea035f 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_ecs.mdx b/api_docs/kbn_securitysolution_ecs.mdx index 4dd583520714e..6cdc69c441a91 100644 --- a/api_docs/kbn_securitysolution_ecs.mdx +++ b/api_docs/kbn_securitysolution_ecs.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-ecs title: "@kbn/securitysolution-ecs" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-ecs plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-ecs'] --- import kbnSecuritysolutionEcsObj from './kbn_securitysolution_ecs.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index dd045c36fcb75..b7a486c967492 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 0d5b772d8a0dd..53238d3b163ae 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 1ae1b5f1ccda9..e7d37388c9f03 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index bbe20a810001c..ed33cedfefe74 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,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 description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 7d5c918352fe6..9f6886c027809 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,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 description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index df64ef56e48f6..00e28e1871679 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 515fbef38847a..2f0133ec06b2f 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index 2bb6eec809af1..e1b4c34903283 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index 69ccb1593f4d8..c4dd45085cb94 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index f85008faeacc6..3415f5b27fdf6 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 0e2bbcefdc4d8..7fa3b2d99f96e 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 3ba4c88efcf2e..1dd1c6302ebcc 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index b3be901ccd130..50973440f0df3 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index 1354047ee3695..cbb8e8782b039 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 576d0e5930ffd..38f300c94bd1c 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index 3db2e619c3bad..52638c374cde5 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 0ddbad2831322..f83f2d488e04f 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_solution.mdx b/api_docs/kbn_shared_ux_avatar_solution.mdx index 613403ecdf068..a2b598d004748 100644 --- a/api_docs/kbn_shared_ux_avatar_solution.mdx +++ b/api_docs/kbn_shared_ux_avatar_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-solution title: "@kbn/shared-ux-avatar-solution" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-solution plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-solution'] --- import kbnSharedUxAvatarSolutionObj from './kbn_shared_ux_avatar_solution.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index 129b0d1f90c20..0d3f31b8865c0 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx index 0ddb60c7d6611..a5466eca5acc1 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen title: "@kbn/shared-ux-button-exit-full-screen" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen'] --- import kbnSharedUxButtonExitFullScreenObj from './kbn_shared_ux_button_exit_full_screen.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index a1ae33e86b3f9..14bb9e5dc00c2 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 64cc68ecff23d..6d53b085aa5ba 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index 30dd624ba0889..7c677fb1f9a6b 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,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 description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index 4c8a14cb5301d..8b001b1fa983c 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_context.mdx b/api_docs/kbn_shared_ux_file_context.mdx index 1f27f197f6aa7..b5195258c7f7b 100644 --- a/api_docs/kbn_shared_ux_file_context.mdx +++ b/api_docs/kbn_shared_ux_file_context.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-context title: "@kbn/shared-ux-file-context" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-context plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-context'] --- import kbnSharedUxFileContextObj from './kbn_shared_ux_file_context.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image.mdx b/api_docs/kbn_shared_ux_file_image.mdx index d6fa862f09ead..01066d325d4aa 100644 --- a/api_docs/kbn_shared_ux_file_image.mdx +++ b/api_docs/kbn_shared_ux_file_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image title: "@kbn/shared-ux-file-image" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image'] --- import kbnSharedUxFileImageObj from './kbn_shared_ux_file_image.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_image_mocks.mdx b/api_docs/kbn_shared_ux_file_image_mocks.mdx index 36d55cb5d5ca2..f10c380e1ae7b 100644 --- a/api_docs/kbn_shared_ux_file_image_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_image_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-image-mocks title: "@kbn/shared-ux-file-image-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-image-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-image-mocks'] --- import kbnSharedUxFileImageMocksObj from './kbn_shared_ux_file_image_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_mocks.mdx b/api_docs/kbn_shared_ux_file_mocks.mdx index 6b94f4f24bbca..1f009db9e7a52 100644 --- a/api_docs/kbn_shared_ux_file_mocks.mdx +++ b/api_docs/kbn_shared_ux_file_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-mocks title: "@kbn/shared-ux-file-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-mocks'] --- import kbnSharedUxFileMocksObj from './kbn_shared_ux_file_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_picker.mdx b/api_docs/kbn_shared_ux_file_picker.mdx index 060c2573ec94b..d32cc40fd978e 100644 --- a/api_docs/kbn_shared_ux_file_picker.mdx +++ b/api_docs/kbn_shared_ux_file_picker.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-picker title: "@kbn/shared-ux-file-picker" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-picker plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-picker'] --- import kbnSharedUxFilePickerObj from './kbn_shared_ux_file_picker.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_upload.mdx b/api_docs/kbn_shared_ux_file_upload.mdx index bb9b58d2daf61..2456510388acf 100644 --- a/api_docs/kbn_shared_ux_file_upload.mdx +++ b/api_docs/kbn_shared_ux_file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-upload title: "@kbn/shared-ux-file-upload" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-upload plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-upload'] --- import kbnSharedUxFileUploadObj from './kbn_shared_ux_file_upload.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_file_util.mdx b/api_docs/kbn_shared_ux_file_util.mdx index 663368f7eff44..49ba637448c8e 100644 --- a/api_docs/kbn_shared_ux_file_util.mdx +++ b/api_docs/kbn_shared_ux_file_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-file-util title: "@kbn/shared-ux-file-util" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-file-util plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-file-util'] --- import kbnSharedUxFileUtilObj from './kbn_shared_ux_file_util.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app.mdx b/api_docs/kbn_shared_ux_link_redirect_app.mdx index 729eaf3eaaa43..86256fa2df322 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app title: "@kbn/shared-ux-link-redirect-app" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app'] --- import kbnSharedUxLinkRedirectAppObj from './kbn_shared_ux_link_redirect_app.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index 837d0a3e579df..f4e6a2cd3be4c 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown.mdx b/api_docs/kbn_shared_ux_markdown.mdx index 03ee5c2618af1..9b127f869879d 100644 --- a/api_docs/kbn_shared_ux_markdown.mdx +++ b/api_docs/kbn_shared_ux_markdown.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown title: "@kbn/shared-ux-markdown" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown'] --- import kbnSharedUxMarkdownObj from './kbn_shared_ux_markdown.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_markdown_mocks.mdx b/api_docs/kbn_shared_ux_markdown_mocks.mdx index cd35f7e5cf03d..92d102c63395d 100644 --- a/api_docs/kbn_shared_ux_markdown_mocks.mdx +++ b/api_docs/kbn_shared_ux_markdown_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-markdown-mocks title: "@kbn/shared-ux-markdown-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-markdown-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-markdown-mocks'] --- import kbnSharedUxMarkdownMocksObj from './kbn_shared_ux_markdown_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json b/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json index 82ac24347e054..217e49d5572e4 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.devdocs.json @@ -66,7 +66,7 @@ "\nA pure component of an entire page that can be displayed when Kibana \"has no data\", specifically for Analytics." ], "signature": [ - "({ kibanaGuideDocLink, onDataViewCreated, allowAdHocDataView, }: ", + "({ kibanaGuideDocLink, onDataViewCreated, allowAdHocDataView, showPlainSpinner, }: ", "Props", ") => JSX.Element" ], @@ -79,7 +79,7 @@ "id": "def-common.AnalyticsNoDataPage.$1", "type": "Object", "tags": [], - "label": "{\n kibanaGuideDocLink,\n onDataViewCreated,\n allowAdHocDataView,\n}", + "label": "{\n kibanaGuideDocLink,\n onDataViewCreated,\n allowAdHocDataView,\n showPlainSpinner,\n}", "description": [], "signature": [ "Props" 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 dac874e621691..7cdb30d5f9eee 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,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 description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json index ee58f4094a5e3..7bd3c319b4cfd 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json @@ -108,6 +108,54 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "@kbn/shared-ux-page-analytics-no-data-mocks", + "id": "def-common.StorybookMock.serviceArguments.customBranding", + "type": "Object", + "tags": [], + "label": "customBranding", + "description": [], + "path": "packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-page-analytics-no-data-mocks", + "id": "def-common.StorybookMock.serviceArguments.customBranding.hasCustomBranding$", + "type": "Object", + "tags": [], + "label": "hasCustomBranding$", + "description": [], + "path": "packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/shared-ux-page-analytics-no-data-mocks", + "id": "def-common.StorybookMock.serviceArguments.customBranding.hasCustomBranding$.control", + "type": "string", + "tags": [], + "label": "control", + "description": [], + "path": "packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/shared-ux-page-analytics-no-data-mocks", + "id": "def-common.StorybookMock.serviceArguments.customBranding.hasCustomBranding$.defaultValue", + "type": "boolean", + "tags": [], + "label": "defaultValue", + "description": [], + "path": "packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts", + "deprecated": false, + "trackAdoption": false + } + ] + } + ] } ] }, @@ -219,6 +267,24 @@ "children": [], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/shared-ux-page-analytics-no-data-mocks", + "id": "def-common.getServicesMockCustomBranding", + "type": "Function", + "tags": [], + "label": "getServicesMockCustomBranding", + "description": [], + "signature": [ + "() => ", + "AnalyticsNoDataPageServices" + ], + "path": "packages/shared-ux/page/analytics_no_data/mocks/src/jest.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [], diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 246359c3f1671..b8a232f981982 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 12 | 0 | 12 | 0 | +| 17 | 0 | 17 | 0 | ## Common diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json b/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json index e313723153e65..0076a7f37dc80 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.devdocs.json @@ -29,7 +29,7 @@ "\nA page to display when Kibana has no data, prompting a person to add integrations or create a new data view." ], "signature": [ - "({ onDataViewCreated, noDataConfig, allowAdHocDataView, }: ", + "({ onDataViewCreated, noDataConfig, allowAdHocDataView, showPlainSpinner, }: ", "KibanaNoDataPageProps", ") => JSX.Element | null" ], @@ -42,7 +42,7 @@ "id": "def-common.KibanaNoDataPage.$1", "type": "Object", "tags": [], - "label": "{\n onDataViewCreated,\n noDataConfig,\n allowAdHocDataView,\n}", + "label": "{\n onDataViewCreated,\n noDataConfig,\n allowAdHocDataView,\n showPlainSpinner,\n}", "description": [], "signature": [ "KibanaNoDataPageProps" 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 b9c8d7b042dac..450465d2939ca 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,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 description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json index 6baad57602555..2134860750376 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json @@ -293,7 +293,7 @@ "section": "def-common.Params", "text": "Params" }, - ") => { noDataConfig: { solution: any; logo: any; action: { elasticAgent: { title: string; }; }; docsLink: string; }; onDataViewCreated: ", + ") => { showPlainSpinner: boolean; noDataConfig: { solution: any; logo: any; action: { elasticAgent: { title: string; }; }; docsLink: string; }; onDataViewCreated: ", "HandlerFunction", "; }" ], diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 4ab936ba53775..e429bb8e86ec5 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index d67cbc6398ee7..8557219bb0b5a 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 0295d42172b33..370f919ce3167 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 6e7c46200490d..ec18f3876cdeb 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index 5306cc61ad823..7f2b6f240d65e 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index 953a303571a2e..fcf612a947a83 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index c975b40c2d3c0..1f3a59872b448 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index ce5590d9103d9..860162cd68f54 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; 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 66bdc9c0ac3f5..45fb1fa015506 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,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 description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index fea5b27e93efe..fb4e407f3374a 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_not_found.devdocs.json b/api_docs/kbn_shared_ux_prompt_not_found.devdocs.json index 99a5e86068dfb..8b804e94e8b1d 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.devdocs.json +++ b/api_docs/kbn_shared_ux_prompt_not_found.devdocs.json @@ -29,7 +29,7 @@ "\nPredefined `EuiEmptyPrompt` for 404 pages." ], "signature": [ - "({ actions }: NotFoundProps) => JSX.Element" + "({ actions, title, body }: NotFoundProps) => JSX.Element" ], "path": "packages/shared-ux/prompt/not_found/src/not_found_prompt.tsx", "deprecated": false, @@ -40,7 +40,7 @@ "id": "def-common.NotFoundPrompt.$1", "type": "Object", "tags": [], - "label": "{ actions }", + "label": "{ actions, title, body }", "description": [], "signature": [ "NotFoundProps" diff --git a/api_docs/kbn_shared_ux_prompt_not_found.mdx b/api_docs/kbn_shared_ux_prompt_not_found.mdx index 0d60150cd58b8..df687e859cd46 100644 --- a/api_docs/kbn_shared_ux_prompt_not_found.mdx +++ b/api_docs/kbn_shared_ux_prompt_not_found.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-not-found title: "@kbn/shared-ux-prompt-not-found" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-not-found plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-not-found'] --- import kbnSharedUxPromptNotFoundObj from './kbn_shared_ux_prompt_not_found.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index e6ce595ac568e..91505f0b67758 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index 920afbc20a329..6ec532aa2cca2 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index 5baf421f29c62..7d18757c04a7e 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index 5c97689451e8b..ea127e62a8ab8 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index ff2270fc7658b..a0be397983c80 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_slo_schema.devdocs.json b/api_docs/kbn_slo_schema.devdocs.json index e0241d83dc24d..74ba519202778 100644 --- a/api_docs/kbn_slo_schema.devdocs.json +++ b/api_docs/kbn_slo_schema.devdocs.json @@ -520,7 +520,7 @@ "label": "FindSLOResponse", "description": [], "signature": [ - "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }" + "{ page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }" ], "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -535,7 +535,7 @@ "label": "GetSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" ], "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -557,6 +557,21 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.ManageSLOParams", + "type": "Type", + "tags": [], + "label": "ManageSLOParams", + "description": [], + "signature": [ + "{ id: string; }" + ], + "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.SLOResponse", @@ -565,7 +580,7 @@ "label": "SLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; }" ], "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -580,7 +595,7 @@ "label": "SLOWithSummaryResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }" ], "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -665,7 +680,7 @@ "label": "UpdateSLOResponse", "description": [], "signature": [ - "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; }" + "{ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; }" ], "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", "deprecated": false, @@ -1527,7 +1542,9 @@ "section": "def-common.Duration", "text": "Duration" }, - ", string, unknown>; }>; createdAt: ", + ", string, unknown>; }>; enabled: ", + "BooleanC", + "; createdAt: ", "Type", "; updatedAt: ", "Type", @@ -1787,7 +1804,9 @@ "section": "def-common.Duration", "text": "Duration" }, - ", string, unknown>; }>; createdAt: ", + ", string, unknown>; }>; enabled: ", + "BooleanC", + "; createdAt: ", "Type", "; updatedAt: ", "Type", @@ -2092,6 +2111,26 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "@kbn/slo-schema", + "id": "def-common.manageSLOParamsSchema", + "type": "Object", + "tags": [], + "label": "manageSLOParamsSchema", + "description": [], + "signature": [ + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>" + ], + "path": "packages/kbn-slo-schema/src/rest_specs/slo.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, { "parentPluginId": "@kbn/slo-schema", "id": "def-common.objectiveSchema", @@ -2447,7 +2486,9 @@ "section": "def-common.Duration", "text": "Duration" }, - ", string, unknown>; }>; createdAt: ", + ", string, unknown>; }>; enabled: ", + "BooleanC", + "; createdAt: ", "Type", "; updatedAt: ", "Type", @@ -2659,6 +2700,8 @@ }, ", string, unknown>; }>; revision: ", "NumberC", + "; enabled: ", + "BooleanC", "; createdAt: ", "Type", "; updatedAt: ", @@ -2873,7 +2916,9 @@ "section": "def-common.Duration", "text": "Duration" }, - ", string, unknown>; }>; createdAt: ", + ", string, unknown>; }>; enabled: ", + "BooleanC", + "; createdAt: ", "Type", "; updatedAt: ", "Type", @@ -3113,6 +3158,8 @@ }, ", string, unknown>; }>; revision: ", "NumberC", + "; enabled: ", + "BooleanC", "; createdAt: ", "Type", "; updatedAt: ", @@ -3685,7 +3732,9 @@ "section": "def-common.Duration", "text": "Duration" }, - ", string, unknown>; }>; createdAt: ", + ", string, unknown>; }>; enabled: ", + "BooleanC", + "; createdAt: ", "Type", "; updatedAt: ", "Type", diff --git a/api_docs/kbn_slo_schema.mdx b/api_docs/kbn_slo_schema.mdx index e9ef7a7af2377..1e9250cb4a88b 100644 --- a/api_docs/kbn_slo_schema.mdx +++ b/api_docs/kbn_slo_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-slo-schema title: "@kbn/slo-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/slo-schema plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/slo-schema'] --- import kbnSloSchemaObj from './kbn_slo_schema.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 73 | 0 | 73 | 0 | +| 75 | 0 | 75 | 0 | ## Common diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index cae8b252bb919..2d9bf6029fa81 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index d03ada1ae175e..c26b9ae3fb5d9 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index e3a26453bf082..c8d825a25bbee 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 04ac3608174b9..4049c743592eb 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 960d40b23b79c..f1809b7d4e4f1 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 1e2ace57b7990..8a6846b8a6da8 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index 935d7de444987..4e4f03901b4b3 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 44fd681205e76..dbee14eda1fd3 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index 951d9b19af659..42bf35af75490 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_ts_projects.mdx b/api_docs/kbn_ts_projects.mdx index 68479d0480a5f..675f87d17019a 100644 --- a/api_docs/kbn_ts_projects.mdx +++ b/api_docs/kbn_ts_projects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ts-projects title: "@kbn/ts-projects" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ts-projects plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ts-projects'] --- import kbnTsProjectsObj from './kbn_ts_projects.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index 70dad33d210fa..67c01b37c5473 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_actions_browser.mdx b/api_docs/kbn_ui_actions_browser.mdx index 1c2e1f15f9654..16ccdd39ef13a 100644 --- a/api_docs/kbn_ui_actions_browser.mdx +++ b/api_docs/kbn_ui_actions_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-actions-browser title: "@kbn/ui-actions-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-actions-browser plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-actions-browser'] --- import kbnUiActionsBrowserObj from './kbn_ui_actions_browser.devdocs.json'; diff --git a/api_docs/kbn_ui_shared_deps_src.mdx b/api_docs/kbn_ui_shared_deps_src.mdx index 673383ef97b67..81b24c5ffe6e5 100644 --- a/api_docs/kbn_ui_shared_deps_src.mdx +++ b/api_docs/kbn_ui_shared_deps_src.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-shared-deps-src title: "@kbn/ui-shared-deps-src" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-shared-deps-src plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-shared-deps-src'] --- import kbnUiSharedDepsSrcObj from './kbn_ui_shared_deps_src.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 89f4e93704d27..cb1ea4b5b8f52 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index fce46e8052a66..22ff86de7ea75 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index e71ed78870a3b..f2bb5aa9041fd 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index 6e4fd9785a7dc..81793b6da6787 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 00e072693095c..18d029130053e 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index db5e4488f9758..af01a9fbae92d 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index e5fd98c4ccc0b..3a85ede27a7b0 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index 6352f703cc561..9d72153e81fce 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index fe1bcfa772cf6..33fa401d042a7 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index 96d556658dbdf..a324aff5ac673 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index 29a49dcef5bb7..887306f54c954 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index 54828ce874a67..a7822f1638329 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index d627d5d0c6893..e0162e2602462 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index e9199a657935a..678c2884c02ee 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 2096ce8598128..6121094fed9d3 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 713b6e659790a..4c1a8a812b598 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 362ddfc8be3aa..80ea229325c91 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index a0ec73841ae03..692ac23d1ec9f 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index f8db2ec390c7a..def1256f632d4 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index 7372a92b62978..9e606d365b4b7 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index dfad2c51573b2..951f25cb24321 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index e2be5b8a93ce7..5100e87a3357b 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index e935ba982517e..8d96e2b5007fe 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/notifications.mdx b/api_docs/notifications.mdx index 38a0fe38afb2b..bfa39194c308e 100644 --- a/api_docs/notifications.mdx +++ b/api_docs/notifications.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/notifications title: "notifications" image: https://source.unsplash.com/400x175/?github description: API docs for the notifications plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'notifications'] --- import notificationsObj from './notifications.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index d369b808ecb52..79bf3acafe827 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -4353,6 +4353,26 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "observability", + "id": "def-public.ObservabilityPublicPluginsStart.unifiedSearch", + "type": "Object", + "tags": [], + "label": "unifiedSearch", + "description": [], + "signature": [ + { + "pluginId": "unifiedSearch", + "scope": "public", + "docId": "kibUnifiedSearchPluginApi", + "section": "def-public.UnifiedSearchPublicPluginStart", + "text": "UnifiedSearchPublicPluginStart" + } + ], + "path": "x-pack/plugins/observability/public/plugin.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "observability", "id": "def-public.ObservabilityPublicPluginsStart.home", @@ -4633,16 +4653,16 @@ "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.ExistsFilter", - "text": "ExistsFilter" + "section": "def-common.PhraseFilter", + "text": "PhraseFilter" }, " | ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.PhraseFilter", - "text": "PhraseFilter" + "section": "def-common.ExistsFilter", + "text": "ExistsFilter" }, " | ", { @@ -4671,16 +4691,16 @@ "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.ExistsFilter", - "text": "ExistsFilter" + "section": "def-common.PhraseFilter", + "text": "PhraseFilter" }, " | ", { "pluginId": "@kbn/es-query", "scope": "common", "docId": "kibKbnEsQueryPluginApi", - "section": "def-common.PhraseFilter", - "text": "PhraseFilter" + "section": "def-common.ExistsFilter", + "text": "ExistsFilter" }, " | ", { @@ -10002,7 +10022,85 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"GET /api/observability/slos/{id}\"?: ", + { + "pluginId": "@kbn/server-route-repository", + "scope": "public", + "docId": "kibKbnServerRouteRepositoryPluginApi", + "section": "def-public.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"GET /api/observability/slos\"?: ", + { + "pluginId": "@kbn/server-route-repository", + "scope": "public", + "docId": "kibKbnServerRouteRepositoryPluginApi", + "section": "def-public.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /api/observability/slos\", ", + "PartialC", + "<{ query: ", + "PartialC", + "<{ name: ", + "StringC", + "; indicatorTypes: ", + "Type", + "; page: ", + "StringC", + "; perPage: ", + "StringC", + "; sortBy: ", + "UnionC", + "<[", + "LiteralC", + "<\"name\">, ", + "LiteralC", + "<\"indicatorType\">]>; sortDirection: ", + "UnionC", + "<[", + "LiteralC", + "<\"asc\">, ", + "LiteralC", + "<\"desc\">]>; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }, ", { "pluginId": "observability", "scope": "server", @@ -10042,7 +10140,7 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, - "> | undefined; \"GET /api/observability/slos/{id}\"?: ", + "> | undefined; \"POST /api/observability/slos/{id}/enable\"?: ", { "pluginId": "@kbn/server-route-repository", "scope": "public", @@ -10050,7 +10148,7 @@ "section": "def-public.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/observability/slos/{id}\", ", + "<\"POST /api/observability/slos/{id}/enable\", ", "TypeC", "<{ path: ", "TypeC", @@ -10064,7 +10162,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }, ", + ", void, ", { "pluginId": "observability", "scope": "server", @@ -10072,7 +10170,7 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, - "> | undefined; \"GET /api/observability/slos\"?: ", + "> | undefined; \"POST /api/observability/slos/{id}/disable\"?: ", { "pluginId": "@kbn/server-route-repository", "scope": "public", @@ -10080,31 +10178,13 @@ "section": "def-public.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/observability/slos\", ", - "PartialC", - "<{ query: ", - "PartialC", - "<{ name: ", - "StringC", - "; indicatorTypes: ", - "Type", - "; page: ", - "StringC", - "; perPage: ", + "<\"POST /api/observability/slos/{id}/disable\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", "StringC", - "; sortBy: ", - "UnionC", - "<[", - "LiteralC", - "<\"name\">, ", - "LiteralC", - "<\"indicatorType\">]>; sortDirection: ", - "UnionC", - "<[", - "LiteralC", - "<\"asc\">, ", - "LiteralC", - "<\"desc\">]>; }>; }>, ", + "; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -10112,7 +10192,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }, ", + ", void, ", { "pluginId": "observability", "scope": "server", @@ -10656,7 +10736,85 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; }, ", + ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"GET /api/observability/slos/{id}\"?: ", + { + "pluginId": "@kbn/server-route-repository", + "scope": "public", + "docId": "kibKbnServerRouteRepositoryPluginApi", + "section": "def-public.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /api/observability/slos/{id}\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", + "StringC", + "; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteCreateOptions", + "text": "ObservabilityRouteCreateOptions" + }, + "> | undefined; \"GET /api/observability/slos\"?: ", + { + "pluginId": "@kbn/server-route-repository", + "scope": "public", + "docId": "kibKbnServerRouteRepositoryPluginApi", + "section": "def-public.ServerRoute", + "text": "ServerRoute" + }, + "<\"GET /api/observability/slos\", ", + "PartialC", + "<{ query: ", + "PartialC", + "<{ name: ", + "StringC", + "; indicatorTypes: ", + "Type", + "; page: ", + "StringC", + "; perPage: ", + "StringC", + "; sortBy: ", + "UnionC", + "<[", + "LiteralC", + "<\"name\">, ", + "LiteralC", + "<\"indicatorType\">]>; sortDirection: ", + "UnionC", + "<[", + "LiteralC", + "<\"asc\">, ", + "LiteralC", + "<\"desc\">]>; }>; }>, ", + { + "pluginId": "observability", + "scope": "server", + "docId": "kibObservabilityPluginApi", + "section": "def-server.ObservabilityRouteHandlerResources", + "text": "ObservabilityRouteHandlerResources" + }, + ", { page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; enabled: boolean; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }, ", { "pluginId": "observability", "scope": "server", @@ -10696,7 +10854,7 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, - "> | undefined; \"GET /api/observability/slos/{id}\"?: ", + "> | undefined; \"POST /api/observability/slos/{id}/enable\"?: ", { "pluginId": "@kbn/server-route-repository", "scope": "public", @@ -10704,7 +10862,7 @@ "section": "def-public.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/observability/slos/{id}\", ", + "<\"POST /api/observability/slos/{id}/enable\", ", "TypeC", "<{ path: ", "TypeC", @@ -10718,7 +10876,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; }, ", + ", void, ", { "pluginId": "observability", "scope": "server", @@ -10726,7 +10884,7 @@ "section": "def-server.ObservabilityRouteCreateOptions", "text": "ObservabilityRouteCreateOptions" }, - "> | undefined; \"GET /api/observability/slos\"?: ", + "> | undefined; \"POST /api/observability/slos/{id}/disable\"?: ", { "pluginId": "@kbn/server-route-repository", "scope": "public", @@ -10734,31 +10892,13 @@ "section": "def-public.ServerRoute", "text": "ServerRoute" }, - "<\"GET /api/observability/slos\", ", - "PartialC", - "<{ query: ", - "PartialC", - "<{ name: ", - "StringC", - "; indicatorTypes: ", - "Type", - "; page: ", - "StringC", - "; perPage: ", + "<\"POST /api/observability/slos/{id}/disable\", ", + "TypeC", + "<{ path: ", + "TypeC", + "<{ id: ", "StringC", - "; sortBy: ", - "UnionC", - "<[", - "LiteralC", - "<\"name\">, ", - "LiteralC", - "<\"indicatorType\">]>; sortDirection: ", - "UnionC", - "<[", - "LiteralC", - "<\"asc\">, ", - "LiteralC", - "<\"desc\">]>; }>; }>, ", + "; }>; }>, ", { "pluginId": "observability", "scope": "server", @@ -10766,7 +10906,7 @@ "section": "def-server.ObservabilityRouteHandlerResources", "text": "ObservabilityRouteHandlerResources" }, - ", { page: number; perPage: number; total: number; results: ({ id: string; name: string; description: string; indicator: { type: \"sli.apm.transactionDuration\"; params: { environment: string; service: string; transactionType: string; transactionName: string; 'threshold.us': number; } & { index?: string | undefined; }; } | { type: \"sli.apm.transactionErrorRate\"; params: { environment: string; service: string; transactionType: string; transactionName: string; } & { goodStatusCodes?: (\"2xx\" | \"3xx\" | \"4xx\" | \"5xx\")[] | undefined; index?: string | undefined; }; } | { type: \"sli.kql.custom\"; params: { index: string; filter: string; good: string; total: string; }; }; timeWindow: { duration: string; isRolling: boolean; } | { duration: string; calendar: { startTime: string; }; }; budgetingMethod: \"occurrences\" | \"timeslices\"; objective: { target: number; } & { timesliceTarget?: number | undefined; timesliceWindow?: string | undefined; }; revision: number; settings: { timestampField: string; syncDelay: string; frequency: string; }; createdAt: string; updatedAt: string; } & { summary: { status: \"NO_DATA\" | \"HEALTHY\" | \"DEGRADING\" | \"VIOLATED\"; sliValue: number; errorBudget: { initial: number; consumed: number; remaining: number; isEstimated: boolean; }; }; })[]; }, ", + ", void, ", { "pluginId": "observability", "scope": "server", diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index eee80c7d07501..4fdd36a64409b 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Observability UI](https://github.com/orgs/elastic/teams/observability-u | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 606 | 41 | 600 | 32 | +| 607 | 41 | 601 | 32 | ## Client diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index dd794c0fbfba7..1988fe6de618d 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index 96bd8997cd457..f1e1d8e7cd38c 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a
public API | Number of teams | |--------------|----------|------------------------| -| 564 | 464 | 43 | +| 568 | 467 | 44 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 70017 | 527 | 59226 | 1206 | +| 70145 | 526 | 59352 | 1213 | ## Plugin Directory @@ -47,8 +47,9 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | cloudLinks | [Kibana Core](https://github.com/orgs/elastic/teams/@kibana-core) | Adds the links to the Elastic Cloud console | 0 | 0 | 0 | 0 | | | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 17 | 0 | 2 | 2 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | +| | [@elastic/kibana-global-experience](https://github.com/orgs/elastic/teams/@elastic/kibana-global-experience) | Content management app | 5 | 0 | 5 | 0 | | | [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 | 268 | 0 | 264 | 9 | -| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2832 | 17 | 1016 | 0 | +| | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2845 | 17 | 1029 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | customBranding | [global-experience](https://github.com/orgs/elastic/teams/kibana-global-experience) | Enables customization of Kibana | 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 | 107 | 0 | 88 | 1 | @@ -89,7 +90,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [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 | | | [@elastic/kibana-app-services](https://github.com/orgs/elastic/teams/team:AppServicesUx) | File upload, download, sharing, and serving over HTTP implementation in Kibana. | 254 | 1 | 45 | 5 | | | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/@elastic/appex-sharedux) | Simple UI for managing files in Kibana | 2 | 1 | 2 | 0 | -| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1040 | 3 | 935 | 25 | +| | [Fleet](https://github.com/orgs/elastic/teams/fleet) | - | 1068 | 3 | 963 | 26 | | ftrApis | [Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | | [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 | @@ -126,7 +127,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 34 | 0 | 34 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 17 | 0 | 17 | 0 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 2 | 0 | 2 | 1 | -| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 606 | 41 | 600 | 32 | +| | [Observability UI](https://github.com/orgs/elastic/teams/observability-ui) | - | 607 | 41 | 601 | 32 | | | [Security asset management](https://github.com/orgs/elastic/teams/security-asset-management) | - | 24 | 0 | 24 | 7 | | painlessLab | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Presentation Utility Plugin is a set of common, shared components and toolkits for solutions within the Presentation space, (e.g. Dashboards, Canvas). | 217 | 7 | 161 | 11 | @@ -134,7 +135,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Reporting Services enables applications to feature reports that the user can automate with Watcher and download later. | 36 | 0 | 16 | 0 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 21 | 0 | 21 | 0 | -| | [RAC](https://github.com/orgs/elastic/teams/rac) | - | 241 | 0 | 213 | 10 | +| | [RAC](https://github.com/orgs/elastic/teams/rac) | - | 251 | 0 | 223 | 11 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 24 | 0 | 19 | 2 | | | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 196 | 2 | 155 | 5 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 16 | 0 | 16 | 0 | @@ -150,7 +151,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 7 | 0 | 7 | 1 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds URL Service and sharing capabilities to Kibana | 118 | 0 | 59 | 10 | | | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 22 | 1 | 22 | 1 | -| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 260 | 0 | 64 | 0 | +| | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides the Spaces feature, which allows saved objects to be organized into meaningful categories. | 261 | 0 | 65 | 0 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 12 | 0 | 12 | 2 | | | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 4 | 0 | 4 | 0 | | synthetics | [Uptime](https://github.com/orgs/elastic/teams/uptime) | This plugin visualizes data from Synthetics and Heartbeat, and integrates with other Observability solutions. | 0 | 0 | 0 | 0 | @@ -159,11 +160,11 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 31 | 0 | 26 | 6 | | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 1 | 0 | 1 | 0 | | | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 5 | 0 | 0 | 0 | -| | [Protections Experience Team](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 35 | 0 | 15 | 5 | +| | [Protections Experience Team](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 35 | 0 | 14 | 5 | | | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 257 | 1 | 214 | 20 | | | [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) | - | 587 | 11 | 558 | 53 | +| | [Response Ops](https://github.com/orgs/elastic/teams/response-ops) | - | 589 | 11 | 560 | 53 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds UI Actions service to Kibana | 134 | 2 | 92 | 9 | | | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Extends UI Actions plugin with more functionality | 206 | 0 | 140 | 9 | | | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | Contains functionality for the field list which can be integrated into apps | 267 | 0 | 242 | 7 | @@ -217,6 +218,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 3 | 0 | 3 | 0 | | | [Owner missing] | - | 62 | 0 | 17 | 1 | | | [Owner missing] | - | 2 | 0 | 2 | 0 | +| | [Owner missing] | - | 2 | 0 | 2 | 1 | +| | [Owner missing] | - | 37 | 0 | 36 | 0 | | | [Owner missing] | - | 106 | 0 | 80 | 1 | | | Kibana Core | - | 73 | 0 | 44 | 9 | | | Kibana Core | - | 24 | 0 | 24 | 0 | @@ -251,7 +254,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 5 | 0 | 5 | 0 | | | [Owner missing] | - | 4 | 0 | 4 | 0 | | | [Owner missing] | - | 6 | 0 | 1 | 0 | -| | [Owner missing] | - | 6 | 0 | 6 | 0 | +| | [Owner missing] | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 6 | 0 | 6 | 1 | | | [Owner missing] | - | 4 | 0 | 4 | 0 | | | Kibana Core | - | 9 | 0 | 3 | 0 | @@ -393,7 +396,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 4 | 0 | 4 | 0 | | | [Owner missing] | - | 27 | 0 | 14 | 1 | | | Kibana Core | - | 7 | 0 | 3 | 0 | -| | [Owner missing] | - | 250 | 2 | 192 | 13 | +| | [Owner missing] | - | 250 | 1 | 192 | 15 | | | Kibana Core | - | 11 | 0 | 11 | 0 | | | [Owner missing] | - | 2 | 0 | 1 | 0 | | | [Owner missing] | - | 20 | 0 | 16 | 0 | @@ -401,7 +404,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 29 | 0 | 29 | 1 | | | [Owner missing] | - | 1 | 0 | 0 | 0 | | | [Owner missing] | - | 6 | 0 | 0 | 0 | -| | [Owner missing] | - | 58 | 0 | 56 | 1 | +| | [Owner missing] | - | 52 | 0 | 50 | 2 | | | [Owner missing] | - | 19 | 1 | 12 | 0 | | | [Owner missing] | - | 3 | 0 | 3 | 0 | | | Kibana Core | - | 1 | 0 | 1 | 0 | @@ -415,7 +418,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 13 | 0 | 13 | 0 | | | [Owner missing] | - | 67 | 0 | 62 | 5 | | | [Owner missing] | - | 32 | 2 | 28 | 0 | -| | [Owner missing] | - | 86 | 0 | 85 | 0 | +| | [Owner missing] | - | 108 | 0 | 107 | 0 | | | [Owner missing] | - | 7 | 0 | 5 | 0 | | | Kibana Core | - | 27 | 0 | 1 | 2 | | | Kibana Core | - | 8 | 0 | 8 | 0 | @@ -443,7 +446,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 13 | 0 | 9 | 0 | | | [Owner missing] | - | 6 | 0 | 6 | 1 | | | [Owner missing] | - | 13 | 2 | 8 | 0 | -| | [Owner missing] | - | 99 | 0 | 96 | 0 | +| | [Owner missing] | - | 100 | 0 | 97 | 0 | | | [Owner missing] | Security Solution auto complete | 56 | 1 | 41 | 1 | | | [Owner missing] | - | 341 | 1 | 337 | 32 | | | [Owner missing] | security solution elastic search utilities to use across plugins such lists, security_solution, cases, etc... | 67 | 0 | 61 | 1 | @@ -482,7 +485,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 2 | 0 | 2 | 1 | | | [Owner missing] | - | 32 | 0 | 31 | 0 | | | [Owner missing] | - | 14 | 0 | 5 | 1 | -| | [Owner missing] | - | 12 | 0 | 12 | 0 | +| | [Owner missing] | - | 17 | 0 | 17 | 0 | | | [Owner missing] | - | 8 | 0 | 3 | 0 | | | [Owner missing] | - | 25 | 0 | 24 | 0 | | | [Owner missing] | - | 11 | 0 | 6 | 0 | @@ -500,7 +503,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | | [Owner missing] | - | 2 | 0 | 0 | 0 | | | [Owner missing] | - | 15 | 0 | 4 | 0 | | | [Owner missing] | - | 9 | 0 | 3 | 0 | -| | [Owner missing] | SLO io-ts schema definition and common models shared between public and server. | 73 | 0 | 73 | 0 | +| | [Owner missing] | SLO io-ts schema definition and common models shared between public and server. | 75 | 0 | 75 | 0 | | | [Owner missing] | - | 20 | 0 | 12 | 0 | | | Kibana Core | - | 97 | 1 | 64 | 1 | | | [Owner missing] | - | 4 | 0 | 2 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index ad4d121202873..9d16b8a845090 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 148ce4163f187..19b3f879685c7 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index f86b5ea1ad68b..ecf6c10ae12cc 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index 10a64ef76481e..02ae49c2688d7 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 15e34aff07d77..a862b950c7bcc 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.devdocs.json b/api_docs/rule_registry.devdocs.json index 34a50ca6688ef..f59c56fd76922 100644 --- a/api_docs/rule_registry.devdocs.json +++ b/api_docs/rule_registry.devdocs.json @@ -2475,7 +2475,7 @@ "signature": [ "(request: TSearchRequest) => Promise<", + ", TAlertDoc = Partial> & OutputOf>>>(request: TSearchRequest) => Promise<", { "pluginId": "@kbn/es-types", "scope": "common", @@ -2483,7 +2483,7 @@ "section": "def-common.ESSearchResponse", "text": "ESSearchResponse" }, - "> & OutputOf>>, TSearchRequest, { restTotalHitsAsInt: false; }>>" + ">" ], "path": "x-pack/plugins/rule_registry/server/rule_data_client/types.ts", "deprecated": false, @@ -3112,7 +3112,7 @@ "label": "getAlertByAlertUuid", "description": [], "signature": [ - "(alertUuid: string) => { [x: string]: any; } | null" + "(alertUuid: string) => Promise<{ [id: string]: any; }[] | null>" ], "path": "x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts", "deprecated": false, @@ -3295,6 +3295,86 @@ "trackAdoption": false } ] + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.PersistenceServices.alertWithSuppression", + "type": "Function", + "tags": [], + "label": "alertWithSuppression", + "description": [], + "signature": [ + "(alerts: { _id: string; _source: T; }[], suppressionWindow: string, enrichAlerts?: ((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined, currentTimeOverride?: Date | undefined) => Promise, \"alertsWereTruncated\">>" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-server.PersistenceServices.alertWithSuppression.$1", + "type": "Array", + "tags": [], + "label": "alerts", + "description": [], + "signature": [ + "{ _id: string; _source: T; }[]" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.PersistenceServices.alertWithSuppression.$2", + "type": "string", + "tags": [], + "label": "suppressionWindow", + "description": [], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.PersistenceServices.alertWithSuppression.$3", + "type": "Function", + "tags": [], + "label": "enrichAlerts", + "description": [], + "signature": [ + "((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.PersistenceServices.alertWithSuppression.$4", + "type": "Object", + "tags": [], + "label": "currentTimeOverride", + "description": [], + "signature": [ + "Date | undefined" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false @@ -3973,6 +4053,87 @@ "trackAdoption": false, "initialIsOpen": false }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertService", + "type": "Type", + "tags": [], + "label": "SuppressedAlertService", + "description": [], + "signature": [ + "(alerts: { _id: string; _source: T; }[], suppressionWindow: string, enrichAlerts?: ((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined, currentTimeOverride?: Date | undefined) => Promise, \"alertsWereTruncated\">>" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertService.$1", + "type": "Array", + "tags": [], + "label": "alerts", + "description": [], + "signature": [ + "{ _id: string; _source: T; }[]" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertService.$2", + "type": "string", + "tags": [], + "label": "suppressionWindow", + "description": [], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertService.$3", + "type": "Function", + "tags": [], + "label": "enrichAlerts", + "description": [], + "signature": [ + "((alerts: { _id: string; _source: T; }[], params: { spaceId: string; }) => Promise<{ _id: string; _source: T; }[]>) | undefined" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "ruleRegistry", + "id": "def-server.SuppressedAlertService.$4", + "type": "Object", + "tags": [], + "label": "currentTimeOverride", + "description": [], + "signature": [ + "Date | undefined" + ], + "path": "x-pack/plugins/rule_registry/server/utils/persistence_types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, { "parentPluginId": "ruleRegistry", "id": "def-server.Version", diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 2bec837f88bb2..d8acccaefd520 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; @@ -21,7 +21,7 @@ Contact [RAC](https://github.com/orgs/elastic/teams/rac) for questions regarding | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 241 | 0 | 213 | 10 | +| 251 | 0 | 223 | 11 | ## Server diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 795ac6a0fb15d..7f8ba0eca103c 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 7123a0630fdc9..b653d951059d9 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index a85d08d1dd6cb..3b4f7e08db7f3 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index bcf415146653e..7403994b1c81f 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index 7e69c9aa7f330..3875315e6508c 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index fc23fab122614..6d16aefa4a78f 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.devdocs.json b/api_docs/saved_search.devdocs.json index ba9542747b9ab..44ff3e2ef982f 100644 --- a/api_docs/saved_search.devdocs.json +++ b/api_docs/saved_search.devdocs.json @@ -394,7 +394,7 @@ "section": "def-common.SearchSourceFields", "text": "SearchSourceFields" }, - "[K]; getActiveIndexFilter: () => string[]; getOwnField: any[]; getOwnField: (params: Record, replace?: boolean | undefined) => void; getFlyoutComponent: () => React.NamedExoticComponent<", + "{ canWriteBlocklist: boolean; exceptionListApiClient: unknown; useSetUrlParams: () => (params: Record, replace?: boolean | undefined) => void; getFlyoutComponent: () => React.NamedExoticComponent<", "BlockListFlyoutProps", ">; getFormComponent: () => React.NamedExoticComponent<", "BlockListFormProps", diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index fdefb06e38d46..e86ae35f8506e 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Protections Experience Team](https://github.com/orgs/elastic/teams/prot | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 35 | 0 | 15 | 5 | +| 35 | 0 | 14 | 5 | ## Client diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index efdbc8646e154..139941e426aca 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index d47d5255bcf4f..a869cb9acf0b2 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.devdocs.json b/api_docs/triggers_actions_ui.devdocs.json index 8beabd8957f66..2692b9d183efb 100644 --- a/api_docs/triggers_actions_ui.devdocs.json +++ b/api_docs/triggers_actions_ui.devdocs.json @@ -3546,6 +3546,20 @@ "deprecated": false, "trackAdoption": false }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.AlertsTableProps.showAlertStatusWithFlapping", + "type": "CompoundType", + "tags": [], + "label": "showAlertStatusWithFlapping", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/types.ts", + "deprecated": false, + "trackAdoption": false + }, { "parentPluginId": "triggersActionsUi", "id": "def-public.AlertsTableProps.trailingControlColumns", @@ -8707,6 +8721,22 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "triggersActionsUi", + "id": "def-public.TriggersAndActionsUIPublicPluginStart.getRulesSettingsLink", + "type": "Function", + "tags": [], + "label": "getRulesSettingsLink", + "description": [], + "signature": [ + "() => React.ReactElement>" + ], + "path": "x-pack/plugins/triggers_actions_ui/public/plugin.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] } ], "lifecycle": "start", diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index 0eb4b68b7548e..311bcb693ec3f 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Response Ops](https://github.com/orgs/elastic/teams/response-ops) for q | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 587 | 11 | 558 | 53 | +| 589 | 11 | 560 | 53 | ## Client diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 495102dc60bea..47e64697da7e4 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 53e3048ef591a..d965eb5ced992 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 2a5c72c19a055..7f7cf259143f0 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_histogram.mdx b/api_docs/unified_histogram.mdx index a23bcfb92f3d4..78d6c39a64f3e 100644 --- a/api_docs/unified_histogram.mdx +++ b/api_docs/unified_histogram.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedHistogram title: "unifiedHistogram" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedHistogram plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedHistogram'] --- import unifiedHistogramObj from './unified_histogram.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index 03f7841649b46..3619bb67bc237 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index 31747fe10f66a..e7a8c05972a36 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 4b0a87e5f55a3..727e129a4968e 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index 22d3654c6a69d..9629ff388d96f 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index 22d7e86e365a2..5d590d242f97f 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index f5d28f485cf74..e324433932df1 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 0b3d5343a149e..69d71a491e556 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index 672d59a578bc6..95897099a6762 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 827923c9236e3..98381d286bc43 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index eef6fd0a968e6..fb77b045a1cfe 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 2052315ac60fc..3bcee400921c9 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index 9a668ca887a6f..41480c69677c7 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 09a9a548e978c..97978aaa2942c 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 6495677661504..9a0930c3b76b4 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 20790046ae6e9..78a7b2e97e105 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 2d7bc6c01527c..44996f2eb442d 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2023-01-30 +date: 2023-02-02 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; diff --git a/docs/api-generated/README.md b/docs/api-generated/README.md index 97a94e512e5c6..97fd32119b8bc 100644 --- a/docs/api-generated/README.md +++ b/docs/api-generated/README.md @@ -25,7 +25,7 @@ or a similar tool that can generate HTML output from OAS. . Rename the output files. For example: ``` - mv $GIT_HOME/kibana/docs/api-generated/rules/index.html $GIT_HOME/kibana/docs/api-generated/rules/rule-apis-passthru.asciidoc + mv $GIT_HOME/kibana/docs/api-generated/rules/index.html $GIT_HOME/kibana/docs/api-generated/rules/rule-apis-passthru.asciidoc mv $GIT_HOME/kibana/docs/api-generated/cases/index.html $GIT_HOME/kibana/docs/api-generated/cases/case-apis-passthru.asciidoc mv $GIT_HOME/kibana/docs/api-generated/connectors/index.html $GIT_HOME/kibana/docs/api-generated/connectors/connector-apis-passthru.asciidoc mv $GIT_HOME/kibana/docs/api-generated/machine-learning/index.html $GIT_HOME/kibana/docs/api-generated/machine-learning/ml-apis-passthru.asciidoc diff --git a/docs/api-generated/cases/case-apis-passthru.asciidoc b/docs/api-generated/cases/case-apis-passthru.asciidoc index 5801fe57ec075..d6d5b0d589ac1 100644 --- a/docs/api-generated/cases/case-apis-passthru.asciidoc +++ b/docs/api-generated/cases/case-apis-passthru.asciidoc @@ -78,7 +78,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -198,7 +198,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -306,7 +306,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -362,7 +362,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -410,7 +410,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -482,7 +482,6 @@ Any modifications made to this file will be overwritten.
{
   "userActions" : [ {
     "owner" : "cases",
-    "case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
     "action" : "create",
     "created_at" : "2022-05-13T09:16:17.416Z",
     "id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
@@ -497,7 +496,6 @@ Any modifications made to this file will be overwritten.
     "version" : "WzM1ODg4LDFd"
   }, {
     "owner" : "cases",
-    "case_id" : "22df07d0-03b1-11ed-920c-974bfa104448",
     "action" : "create",
     "created_at" : "2022-05-13T09:16:17.416Z",
     "id" : "22fd3e30-03b1-11ed-920c-974bfa104448",
@@ -1519,7 +1517,7 @@ Any modifications made to this file will be overwritten.
     
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -1639,7 +1637,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -1740,7 +1738,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -1862,7 +1860,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -1984,7 +1982,7 @@ Any modifications made to this file will be overwritten.
kbn-xsrf (required)
-
Header Parameter — default: null
+
Header Parameter — Cross-site request forgery protection default: null
@@ -3001,7 +2999,6 @@ Any modifications made to this file will be overwritten.
action
-
case_id
comment_id
created_at
Date format: date-time
created_by
@@ -3009,7 +3006,9 @@ Any modifications made to this file will be overwritten.
owner
payload
version
-
type
+
type
String The type of action.
+
Enum:
+
assignees
create_case
comment
connector
description
pushed
tags
title
status
settings
severity
diff --git a/docs/management/action-types.asciidoc b/docs/management/action-types.asciidoc index 9a48f3c9b9ae4..288c0d1fe0c66 100644 --- a/docs/management/action-types.asciidoc +++ b/docs/management/action-types.asciidoc @@ -74,6 +74,10 @@ a| <> a| <> | Send actionable alerts to on-call xMatters resources. + +a| <> + +| Trigger a Torq workflow. |=== [NOTE] @@ -141,6 +145,9 @@ image::images/connector-select-type.png[Connector select type] After you create a connector, it is available for use any time you set up an action in the current space. +For out-of-the-box and standardized connectors, refer to +<>. + [float] [[importing-and-exporting-connectors]] === Importing and exporting connectors @@ -157,13 +164,6 @@ button appears in *{connectors-ui}*. [role="screenshot"] image::images/connectors-with-missing-secrets.png[Connectors with missing secrets] -[float] -[[create-connectors]] -=== Preconfigured connectors - -For out-of-the-box and standardized connectors, you can <> -before {kib} starts. - [float] [[montoring-connectors]] === Monitoring connectors diff --git a/docs/management/connectors/action-types/server-log.asciidoc b/docs/management/connectors/action-types/server-log.asciidoc index 7d9171ca99ed8..dca6eee379b52 100644 --- a/docs/management/connectors/action-types/server-log.asciidoc +++ b/docs/management/connectors/action-types/server-log.asciidoc @@ -1,50 +1,64 @@ -[role="xpack"] [[server-log-action-type]] -=== Server log connector and action +== Server log connector and action ++++ Server log ++++ -This connector writes an entry to the {kib} server log. +A server log connector writes an entry to the {kib} server log. -[float] -[[server-log-connector-configuration]] -==== Connector configuration - -Server log connectors have the following configuration properties. - -Name:: The name of the connector. +You can create a server log connector in {kib} or by using the +<>. If you are running {kib} +on-prem, you can also create a preconfigured server log connector. [float] -[[Preconfigured-server-log-configuration]] -==== Preconfigured connector type +[[server-log-connector-configuration]] +=== Connector configuration -[source,text] --- - my-server-log: - name: preconfigured-server-log-connector-type - actionTypeId: .server-log --- +Server log connectors do not have any configuration properties other than a name. [float] [[define-serverlog-ui]] -==== Define connector in {stack-manage-app} +=== Create a connector in {kib} -Define Server log connector properties. +You can create a server log connector in *{stack-manage-app} > {connectors-ui}* +or as needed when you're creating a rule. For example: [role="screenshot"] image::management/connectors/images/serverlog-connector.png[Server log connector] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. -Test Server log action parameters. +[float] +[[preconfigured-server-log-configuration]] +=== Create a preconfigured connector -[role="screenshot"] -image::management/connectors/images/serverlog-params-test.png[Server log params test] +If you are running {kib} on-prem, you can define a server log connector by +adding `xpack.actions.preconfigured` settings to your `kibana.yml` file. +For example: + +[source,text] +-- +xpack.actions.preconfigured: + my-server-log: + name: preconfigured-server-log-connector-type + actionTypeId: .server-log +-- + +For more information, go to <>. [float] [[server-log-action-configuration]] -==== Action configuration +=== Test the connector -Server log actions have the following properties. +You can test your server log connector with the +<> or as you're creating or editing +the connector in {kib}. For example: + +[role="screenshot"] +image::management/connectors/images/serverlog-params-test.png[Server log connector test] +// NOTE: This is an autogenerated screenshot. Do not edit it directly. + +Server log actions have the following properties: Message:: The message to log. Level:: The log level of the message: `trace`, `debug`, `info`, `warn`, `error` or `fatal`. Defaults to `info`. + diff --git a/docs/management/connectors/action-types/torq.asciidoc b/docs/management/connectors/action-types/torq.asciidoc new file mode 100644 index 0000000000000..70ac19be25ec3 --- /dev/null +++ b/docs/management/connectors/action-types/torq.asciidoc @@ -0,0 +1,65 @@ +[role="xpack"] +[[torq-action-type]] +=== Torq connector and action +++++ +Torq +++++ + +The Torq connector uses a Torq webhook to trigger workflows with Kibana actions. + +[float] +[[torq-connector-configuration]] +==== Connector configuration +Torq connectors have the following configuration properties. + +Name:: The name of the connector. The name is used to identify a connector in the Stack Management UI connector listing, and in the connector list when configuring an action. + +Torq endpoint URL:: Endpoint URL (webhook) of the Elastic Security integration you created in Torq. + +Torq authentication header secret:: Secret of the webhook authentication header. + +[float] +[[Preconfigured-torq-configuration]] +==== Preconfigured connector type + +[source,yaml] +-- + my-torq: + name: preconfigured-torq-connector-type + actionTypeId: .torq + config: + webhookIntegrationUrl: https://hooks.torq.io/v1/somehook + secrets: + token: mytorqtoken +-- + +Config defines information for the connector type. + +`webhookIntegrationUrl`:: An address that corresponds to **Torq endpoint URL**. + +Secrets defines sensitive information for the connector type. + +`token`:: A string that corresponds to **Torq authentication header secret**. + +[float] +[[define-torq-ui]] +==== Define connector in Stack Management + +Define Torq connector properties. + +[role="screenshot"] +image::management/connectors/images/torq-configured-connector.png[configured Torq connector] + +Test Torq action parameters. + +[role="screenshot"] +image::management/connectors/images/torq-connector-test.png[Torq connector test] + +[float] +[[torq-action-configuration]] +==== Action configuration + + Torq actions have the following configuration properties. + + Body:: JSON payload to send to Torq. + diff --git a/docs/management/connectors/images/pre-configured-connectors-view-screen.png b/docs/management/connectors/images/pre-configured-connectors-view-screen.png deleted file mode 100644 index b2d00b307000e..0000000000000 Binary files a/docs/management/connectors/images/pre-configured-connectors-view-screen.png and /dev/null differ diff --git a/docs/management/connectors/images/pre-configured-connectors-managing.png b/docs/management/connectors/images/preconfigured-connectors-managing.png similarity index 100% rename from docs/management/connectors/images/pre-configured-connectors-managing.png rename to docs/management/connectors/images/preconfigured-connectors-managing.png diff --git a/docs/management/connectors/images/serverlog-connector.png b/docs/management/connectors/images/serverlog-connector.png index 983bb6afadd65..cc0b8745b2d6e 100644 Binary files a/docs/management/connectors/images/serverlog-connector.png and b/docs/management/connectors/images/serverlog-connector.png differ diff --git a/docs/management/connectors/images/serverlog-params-test.png b/docs/management/connectors/images/serverlog-params-test.png index 762721c7ead45..789381949bd43 100644 Binary files a/docs/management/connectors/images/serverlog-params-test.png and b/docs/management/connectors/images/serverlog-params-test.png differ diff --git a/docs/management/connectors/images/torq-configured-connector.png b/docs/management/connectors/images/torq-configured-connector.png new file mode 100644 index 0000000000000..1732276fa4f23 Binary files /dev/null and b/docs/management/connectors/images/torq-configured-connector.png differ diff --git a/docs/management/connectors/images/torq-connector-test.png b/docs/management/connectors/images/torq-connector-test.png new file mode 100644 index 0000000000000..13f416a1486c6 Binary files /dev/null and b/docs/management/connectors/images/torq-connector-test.png differ diff --git a/docs/management/connectors/index.asciidoc b/docs/management/connectors/index.asciidoc index b443ffd967a6f..7a3f8f9cb927c 100644 --- a/docs/management/connectors/index.asciidoc +++ b/docs/management/connectors/index.asciidoc @@ -5,14 +5,15 @@ include::action-types/jira.asciidoc[] include::action-types/teams.asciidoc[] include::action-types/opsgenie.asciidoc[] include::action-types/pagerduty.asciidoc[] -include::action-types/server-log.asciidoc[] +include::action-types/server-log.asciidoc[leveloffset=+1] include::action-types/servicenow.asciidoc[leveloffset=+1] include::action-types/servicenow-sir.asciidoc[leveloffset=+1] include::action-types/servicenow-itom.asciidoc[leveloffset=+1] include::action-types/swimlane.asciidoc[] include::action-types/slack.asciidoc[] include::action-types/tines.asciidoc[leveloffset=+1] +include::action-types/torq.asciidoc[] include::action-types/webhook.asciidoc[] include::action-types/cases-webhook.asciidoc[leveloffset=+1] include::action-types/xmatters.asciidoc[] -include::pre-configured-connectors.asciidoc[] +include::pre-configured-connectors.asciidoc[leveloffset=+1] diff --git a/docs/management/connectors/pre-configured-connectors.asciidoc b/docs/management/connectors/pre-configured-connectors.asciidoc index ad580d87e712b..43643f0f611ba 100644 --- a/docs/management/connectors/pre-configured-connectors.asciidoc +++ b/docs/management/connectors/pre-configured-connectors.asciidoc @@ -1,24 +1,28 @@ -[role="xpack"] [[pre-configured-connectors]] -=== Preconfigured connectors +== Preconfigured connectors -You can preconfigure a connector to have all the information it needs prior to -startup by adding it to the `kibana.yml` file. +If you are running {kib} on-prem, you can preconfigure a connector to have all +the information it needs prior to startup by adding it to the `kibana.yml` file. + +NOTE: {ess} provides a preconfigured email connector but you cannot create +additional preconfigured connectors. Preconfigured connectors offer the following benefits: -- Require no setup. Configuration and credentials needed to execute an -action are predefined, including the connector name and ID. +- Require no setup. Configuration and credentials needed to run an action are +predefined, including the connector name and ID. - Appear in all spaces because they are not saved objects. - Cannot be edited or deleted. [float] -[[preconfigured-connector-example]] -==== Preconfigured connectors example +[[create-preconfigured-connectors]] +=== Create preconfigured connectors + +Add `xpack.actions.preconfigured` settings to your `kibana.yml` file. The +settings vary depending on which type of connector you're adding. -This example shows a valid configuration for -two out-of-the box connectors: <> and -<>. +This example shows a valid configuration for a Slack connector and a Webhook +connector: ```js xpack.actions.preconfigured: @@ -50,31 +54,29 @@ two out-of-the box connectors: <> and [NOTE] ============================================== Sensitive properties, such as passwords, can also be stored in the -<>. +<>. ============================================== [float] [[build-in-preconfigured-connectors]] -==== Built-in preconfigured connectors +=== Built-in preconfigured connectors {kib} provides the following built-in preconfigured connectors: -* <> -* <> +* <> +* <> [float] [[managing-pre-configured-connectors]] -==== View preconfigured connectors +=== View preconfigured connectors When you open the main menu, click *{stack-manage-app} > {connectors-ui}*. Preconfigured connectors appear regardless of which space you are in. They are tagged as “preconfigured”, and you cannot delete them. [role="screenshot"] -image::images/pre-configured-connectors-managing.png[Connectors managing tab with pre-configured] +image::images/preconfigured-connectors-managing.png[Connectors managing tab with pre-configured] Clicking a preconfigured connector shows the description, but not the -configuration. A message indicates that this is a preconfigured connector. +configuration. -[role="screenshot"] -image::images/pre-configured-connectors-view-screen.png[Pre-configured connector view details] diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index 0f7763379e560..a2ac56be64ebf 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -131,7 +131,7 @@ A list of allowed email domains which can be used with the email connector. When WARNING: This feature is available in {kib} 7.17.4 and 8.3.0 onwards but is not supported in {kib} 8.0, 8.1 or 8.2. As such, this setting should be removed before upgrading from 7.17 to 8.0, 8.1 or 8.2. It is possible to configure the settings in 7.17.4 and then upgrade to 8.3.0 directly. `xpack.actions.enabledActionTypes` {ess-icon}:: -A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.tines`, `.xmatters`, and `.webhook`. An empty list `[]` will disable all action types. +A list of action types that are enabled. It defaults to `[*]`, enabling all types. The names for built-in {kib} action types are prefixed with a `.` and include: `.email`, `.index`, `.jira`, `.opsgenie`, `.pagerduty`, `.resilient`, `.server-log`, `.servicenow`, .`servicenow-itom`, `.servicenow-sir`, `.slack`, `.swimlane`, `.teams`, `.tines`, `.torq`, `.xmatters`, and `.webhook`. An empty list `[]` will disable all action types. + Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function. diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index cb3d84af79150..e4fd9111b216a 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -380,6 +380,10 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | User has accessed a case comment. | `failure` | User is not authorized to access a case comment. +.2+| `case_comment_bulk_get` +| `success` | User has accessed multiple case comments. +| `failure` | User is not authorized to access multiple case comments. + .2+| `case_comment_get_all` | `success` | User has accessed case comments. | `failure` | User is not authorized to access case comments. @@ -404,7 +408,6 @@ Refer to the corresponding {es} logs for potential write errors. | `success` | User has accessed the user activity of a case. | `failure` | User is not authorized to access the user activity of a case. - .2+| `case_user_actions_find` | `success` | User has accessed the user activity of a case as part of a search operation. | `failure` | User is not authorized to access the user activity of a case. diff --git a/package.json b/package.json index 884f16533f16b..cccd5253cc381 100644 --- a/package.json +++ b/package.json @@ -421,8 +421,8 @@ "@opentelemetry/semantic-conventions": "^1.4.0", "@reduxjs/toolkit": "1.7.2", "@slack/webhook": "^5.0.4", - "@tanstack/react-query": "^4.20.9", - "@tanstack/react-query-devtools": "^4.20.9", + "@tanstack/react-query": "^4.23.0", + "@tanstack/react-query-devtools": "^4.23.0", "@turf/along": "6.0.1", "@turf/area": "6.0.1", "@turf/bbox": "6.0.1", @@ -583,7 +583,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.7.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.41.3", + "react-hook-form": "^7.42.1", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", diff --git a/packages/core/application/core-application-browser-internal/src/ui/app_container.tsx b/packages/core/application/core-application-browser-internal/src/ui/app_container.tsx index c7fafa498bef4..11899a938da9f 100644 --- a/packages/core/application/core-application-browser-internal/src/ui/app_container.tsx +++ b/packages/core/application/core-application-browser-internal/src/ui/app_container.tsx @@ -126,7 +126,15 @@ export const AppContainer: FC = ({ const AppLoadingPlaceholder: FC<{ showPlainSpinner: boolean }> = ({ showPlainSpinner }) => { if (showPlainSpinner) { - return ; + return ( + + ); } return ( = async ({ logger, scenarioOpts }) => { const BUCKET_SIZE = (MAX_DURATION - MIN_DURATION) / MAX_BUCKETS; - const instances = lodashRange(0, numServices).flatMap((serviceId) => { - const serviceName = `service-${serviceId}`; + const serviceRange = [ + ...lodashRange(0, numServices).map((groupId) => `service-${groupId}`), + '_other', + ]; + const instances = serviceRange.flatMap((serviceName) => { const services = ENVIRONMENTS.map((env) => apm.service(serviceName, env, 'go')); return lodashRange(0, 2).flatMap((serviceNodeId) => diff --git a/packages/kbn-apm-synthtrace/src/scenarios/other_service_group_bucket.ts b/packages/kbn-apm-synthtrace/src/scenarios/other_service_group_bucket.ts deleted file mode 100644 index fbe264d581182..0000000000000 --- a/packages/kbn-apm-synthtrace/src/scenarios/other_service_group_bucket.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 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 { range as lodashRange } from 'lodash'; -import { ApmFields, apm } from '@kbn/apm-synthtrace-client'; -import { Scenario } from '../cli/scenario'; - -const scenario: Scenario = async (runOptions) => { - const { logger } = runOptions; - const numServices = 10; - - return { - generate: ({ range }) => { - const TRANSACTION_TYPES = ['request']; - const ENVIRONMENTS = ['production', 'development']; - - const MIN_DURATION = 10; - const MAX_DURATION = 1000; - - const MAX_BUCKETS = 50; - - const BUCKET_SIZE = (MAX_DURATION - MIN_DURATION) / MAX_BUCKETS; - - const serviceRange = [ - ...lodashRange(0, numServices).map((groupId) => `service-${groupId}`), - '_other', - ]; - - const instances = serviceRange.flatMap((serviceName) => { - const services = ENVIRONMENTS.map((env) => apm.service(serviceName, env, 'go')); - - return lodashRange(0, 2).flatMap((serviceNodeId) => - services.map((service) => service.instance(`${serviceName}-${serviceNodeId}`)) - ); - }); - - const transactionGroupRange = [ - ...lodashRange(0, 10).map((groupId) => `transaction-${groupId}`), - '_other', - ]; - - return range.ratePerMinute(60).generator((timestamp, timestampIndex) => { - return logger.perf( - 'generate_events_for_timestamp ' + new Date(timestamp).toISOString(), - () => { - const events = instances.flatMap((instance) => - transactionGroupRange.flatMap((groupId, groupIndex) => { - const duration = Math.round( - (timestampIndex % MAX_BUCKETS) * BUCKET_SIZE + MIN_DURATION - ); - - return instance - .transaction(groupId, TRANSACTION_TYPES[groupIndex % TRANSACTION_TYPES.length]) - .timestamp(timestamp) - .duration(duration) - .outcome('success' as const); - }) - ); - - return events; - } - ); - }); - }, - }; -}; - -export default scenario; diff --git a/packages/kbn-es-query/src/filters/build_filters/get_filter_params.ts b/packages/kbn-es-query/src/filters/build_filters/get_filter_params.ts index 46585acd9efc0..1b3a0c780b95f 100644 --- a/packages/kbn-es-query/src/filters/build_filters/get_filter_params.ts +++ b/packages/kbn-es-query/src/filters/build_filters/get_filter_params.ts @@ -6,25 +6,26 @@ * Side Public License, v 1. */ -import type { PhrasesFilter } from './phrases_filter'; -import type { PhraseFilter } from './phrase_filter'; -import type { RangeFilter } from './range_filter'; -import { Filter, FILTERS } from './types'; +import { isPhrasesFilter, PhrasesFilter } from './phrases_filter'; +import { isPhraseFilter } from './phrase_filter'; +import { isRangeFilter } from './range_filter'; +import { Filter } from './types'; /** * @internal used only by the filter bar to create filter pills. */ -export function getFilterParams(filter: Filter) { - switch (filter.meta.type) { - case FILTERS.PHRASE: - return (filter as PhraseFilter).meta.params.query; - case FILTERS.PHRASES: - return (filter as PhrasesFilter).meta.params; - case FILTERS.RANGE: - const { gte, gt, lte, lt } = (filter as RangeFilter).meta.params; - return { - from: gte ?? gt, - to: lt ?? lte, - }; +export function getFilterParams(filter: Filter): Filter['meta']['params'] { + if (isPhraseFilter(filter)) { + return filter.meta.params?.query; + } else if (isPhrasesFilter(filter)) { + return (filter as PhrasesFilter).meta.params; + } else if (isRangeFilter(filter) && filter.meta.params) { + const { gte, gt, lte, lt } = filter.meta.params; + return { + from: gte ?? gt, + to: lt ?? lte, + }; + } else { + return filter.meta.params; } } diff --git a/packages/kbn-es-query/src/filters/build_filters/match_all_filter.ts b/packages/kbn-es-query/src/filters/build_filters/match_all_filter.ts index 5e8083c1d1415..41dd8860ab63f 100644 --- a/packages/kbn-es-query/src/filters/build_filters/match_all_filter.ts +++ b/packages/kbn-es-query/src/filters/build_filters/match_all_filter.ts @@ -7,10 +7,11 @@ */ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SerializableRecord } from '@kbn/utility-types'; import { has } from 'lodash'; import type { Filter, FilterMeta } from './types'; -export interface MatchAllFilterMeta extends FilterMeta { +export interface MatchAllFilterMeta extends FilterMeta, SerializableRecord { field: string; formattedValue: string; } diff --git a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts index 4adc8fdc0fdd4..0b990bd9cdfa7 100644 --- a/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts +++ b/packages/kbn-es-query/src/filters/build_filters/phrase_filter.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { SerializableRecord } from '@kbn/utility-types'; import { get, has, isPlainObject } from 'lodash'; import type { Filter, FilterMeta } from './types'; import type { DataViewFieldBase, DataViewBase } from '../../es_query'; @@ -14,10 +15,12 @@ import { hasRangeKeys } from './range_filter'; export type PhraseFilterValue = string | number | boolean; +export interface PhraseFilterMetaParams extends SerializableRecord { + query: PhraseFilterValue; // The unformatted value +} + export type PhraseFilterMeta = FilterMeta & { - params?: { - query: PhraseFilterValue; // The unformatted value - }; + params?: PhraseFilterMetaParams; field?: string; index?: string; }; diff --git a/packages/kbn-es-query/src/filters/build_filters/range_filter.ts b/packages/kbn-es-query/src/filters/build_filters/range_filter.ts index e9bafade964b7..b1b0fdb0590ab 100644 --- a/packages/kbn-es-query/src/filters/build_filters/range_filter.ts +++ b/packages/kbn-es-query/src/filters/build_filters/range_filter.ts @@ -7,7 +7,9 @@ */ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { map, reduce, mapValues, has, get, keys, pickBy } from 'lodash'; -import type { Filter, FilterMeta } from './types'; +import type { SerializableRecord } from '@kbn/utility-types'; +import type { Filter, FilterMeta, FilterMetaParams } from './types'; +import { FILTERS } from './types'; import type { DataViewBase, DataViewFieldBase } from '../../es_query'; const OPERANDS_IN_RANGE = 2; @@ -37,7 +39,7 @@ const dateComparators = { * It is similar, but not identical to estypes.QueryDslRangeQuery * @public */ -export interface RangeFilterParams { +export interface RangeFilterParams extends SerializableRecord { from?: number | string; to?: number | string; gt?: number | string; @@ -53,9 +55,10 @@ export const hasRangeKeys = (params: RangeFilterParams) => ); export type RangeFilterMeta = FilterMeta & { - params: RangeFilterParams; + params?: RangeFilterParams; field?: string; formattedValue?: string; + type: 'range'; }; export type ScriptedRangeFilter = Filter & { @@ -90,7 +93,16 @@ export type RangeFilter = Filter & { * * @public */ -export const isRangeFilter = (filter?: Filter): filter is RangeFilter => has(filter, 'query.range'); +export function isRangeFilter(filter?: Filter): filter is RangeFilter { + if (filter?.meta?.type) return filter.meta.type === FILTERS.RANGE; + return has(filter, 'query.range'); +} + +export function isRangeFilterParams( + params: FilterMetaParams | undefined +): params is RangeFilterParams { + return typeof params === 'object' && get(params, 'type', '') === 'range'; +} /** * @@ -140,7 +152,9 @@ export const buildRangeFilter = ( const totalInfinite = ['gt', 'lt'].reduce((acc, op) => { const key = op in params ? op : `${op}e`; - const isInfinite = Math.abs(get(params, key)) === Infinity; + const value = get(params, key); + const numericValue = typeof value === 'number' ? value : 0; + const isInfinite = Math.abs(numericValue) === Infinity; if (isInfinite) { acc++; @@ -152,7 +166,7 @@ export const buildRangeFilter = ( return acc; }, 0); - const meta: RangeFilterMeta = { + const meta = { index: indexPattern?.id, params: {}, field: field.name, diff --git a/packages/kbn-es-query/src/filters/build_filters/types.ts b/packages/kbn-es-query/src/filters/build_filters/types.ts index 27140c9a72e99..da3404f8621c8 100644 --- a/packages/kbn-es-query/src/filters/build_filters/types.ts +++ b/packages/kbn-es-query/src/filters/build_filters/types.ts @@ -7,10 +7,10 @@ */ import { ExistsFilter } from './exists_filter'; -import { PhrasesFilter } from './phrases_filter'; -import { PhraseFilter } from './phrase_filter'; -import { RangeFilter } from './range_filter'; -import { MatchAllFilter } from './match_all_filter'; +import { PhrasesFilter, PhrasesFilterMeta } from './phrases_filter'; +import { PhraseFilter, PhraseFilterMeta, PhraseFilterMetaParams } from './phrase_filter'; +import { RangeFilter, RangeFilterMeta, RangeFilterParams } from './range_filter'; +import { MatchAllFilter, MatchAllFilterMeta } from './match_all_filter'; /** * A common type for filters supported by this package @@ -50,6 +50,22 @@ export enum FilterStateStore { GLOBAL_STATE = 'globalState', } +export type FilterMetaParams = + | Filter + | Filter[] + | RangeFilterMeta + | RangeFilterParams + | PhraseFilterMeta + | PhraseFilterMetaParams + | PhrasesFilterMeta + | MatchAllFilterMeta + | string + | string[] + | boolean + | boolean[] + | number + | number[]; + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type FilterMeta = { alias?: string | null; @@ -64,7 +80,7 @@ export type FilterMeta = { isMultiIndex?: boolean; type?: string; key?: string; - params?: any; + params?: FilterMetaParams; value?: string; }; diff --git a/packages/kbn-es-query/src/filters/helpers/update_filter.ts b/packages/kbn-es-query/src/filters/helpers/update_filter.ts index ea61545d557be..29241ca1d1b80 100644 --- a/packages/kbn-es-query/src/filters/helpers/update_filter.ts +++ b/packages/kbn-es-query/src/filters/helpers/update_filter.ts @@ -6,16 +6,14 @@ * Side Public License, v 1. */ -import { identity, pickBy } from 'lodash'; - -import type { Filter, FilterMeta, RangeFilterParams } from '..'; - -type FilterOperator = Pick; +import { get } from 'lodash'; +import { isRangeFilterParams } from '../build_filters/range_filter'; +import type { Filter, FilterMeta } from '..'; export const updateFilter = ( filter: Filter, field?: string, - operator?: FilterOperator, + operator?: FilterMeta, params?: Filter['meta']['params'], fieldType?: string ) => { @@ -52,7 +50,7 @@ function updateField(filter: Filter, field?: string) { }; } -function updateWithExistsOperator(filter: Filter, operator?: FilterOperator) { +function updateWithExistsOperator(filter: Filter, operator?: FilterMeta) { return { ...filter, meta: { @@ -68,76 +66,114 @@ function updateWithExistsOperator(filter: Filter, operator?: FilterOperator) { function updateWithIsOperator( filter: Filter, - operator?: FilterOperator, + operator?: FilterMeta, params?: Filter['meta']['params'], fieldType?: string ) { const safeParams = fieldType === 'number' && !params ? 0 : params; - return { - ...filter, - meta: { - ...filter.meta, - negate: operator?.negate, - type: operator?.type, - params: { ...filter.meta.params, query: params }, - value: undefined, - }, - query: { match_phrase: { [filter.meta.key!]: safeParams ?? '' } }, - }; + if (typeof filter.meta.params === 'object') { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params: { ...filter.meta.params, query: params }, + value: undefined, + }, + query: { match_phrase: { [filter.meta.key!]: safeParams ?? '' } }, + }; + } else { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params: { query: params }, + value: undefined, + }, + query: { match_phrase: { [filter.meta.key!]: safeParams ?? '' } }, + }; + } } function updateWithRangeOperator( filter: Filter, - operator: FilterOperator, - rawParams: RangeFilterParams, + operator: FilterMeta, + rawParams: Filter['meta']['params'] | undefined, field: string ) { - const rangeParams = { - ...pickBy(rawParams, identity), - }; - - const params = { - gte: rangeParams?.from, - lt: rangeParams?.to, - }; - - const updatedFilter = { - ...filter, - meta: { - ...filter.meta, - negate: operator?.negate, - type: operator?.type, - params, - }, - query: { - range: { - [field]: params, + if (isRangeFilterParams(rawParams)) { + const { from, to } = rawParams; + const params = { + gte: from, + lt: to, + }; + const updatedFilter = { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params, }, - }, - }; + query: { + range: { + [field]: params, + }, + }, + }; - return updatedFilter; + return updatedFilter; + } else { + const from = get(rawParams, 'from', undefined); + const to = get(rawParams, 'to', undefined); + const params = { + gte: from, + lt: to, + }; + const updatedFilter = { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params, + }, + query: { + range: { + [field]: params, + }, + }, + }; + return updatedFilter; + } } function updateWithIsOneOfOperator( filter: Filter, - operator?: FilterOperator, - params?: Array + operator?: FilterMeta, + params?: Filter['meta']['params'] ) { - return { - ...filter, - meta: { - ...filter.meta, - negate: operator?.negate, - type: operator?.type, - params, - }, - query: { - bool: { - minimum_should_match: 1, - ...filter!.query?.should, - should: params?.map((param) => ({ match_phrase: { [filter.meta.key!]: param } })), + if (Array.isArray(params)) { + return { + ...filter, + meta: { + ...filter.meta, + negate: operator?.negate, + type: operator?.type, + params, }, - }, - }; + query: { + bool: { + minimum_should_match: 1, + ...filter!.query?.should, + should: params?.map((param) => ({ match_phrase: { [filter.meta.key!]: param } })), + }, + }, + }; + } else { + return filter; + } } diff --git a/packages/kbn-es-query/src/filters/stubs/phrase_filter.ts b/packages/kbn-es-query/src/filters/stubs/phrase_filter.ts index 6b818f632af32..482be741ec741 100644 --- a/packages/kbn-es-query/src/filters/stubs/phrase_filter.ts +++ b/packages/kbn-es-query/src/filters/stubs/phrase_filter.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { PhraseFilter, FilterStateStore } from '..'; +import { FilterStateStore } from '..'; -export const phraseFilter: PhraseFilter = { +export const phraseFilter = { meta: { negate: false, index: 'logstash-*', @@ -24,5 +24,7 @@ export const phraseFilter: PhraseFilter = { $state: { store: FilterStateStore.APP_STATE, }, - query: {}, + query: { + match_phrase: {}, + }, }; diff --git a/packages/kbn-es-query/src/filters/stubs/range_filter.ts b/packages/kbn-es-query/src/filters/stubs/range_filter.ts index e2058f8c07359..9eaa7db7e0563 100644 --- a/packages/kbn-es-query/src/filters/stubs/range_filter.ts +++ b/packages/kbn-es-query/src/filters/stubs/range_filter.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { RangeFilter, FilterStateStore } from '..'; +import { FilterStateStore } from '..'; -export const rangeFilter: RangeFilter = { +export const rangeFilter = { meta: { index: 'logstash-*', negate: false, diff --git a/packages/kbn-guided-onboarding/index.ts b/packages/kbn-guided-onboarding/index.ts index 00f2447a1f0ab..9ccaae3901b48 100644 --- a/packages/kbn-guided-onboarding/index.ts +++ b/packages/kbn-guided-onboarding/index.ts @@ -17,6 +17,6 @@ export type { StepConfig, StepDescriptionWithLink, } from './src/types'; -export { GuideCard, InfrastructureLinkCard } from './src/components/landing_page'; -export type { GuideCardUseCase } from './src/components/landing_page'; +export { GuideCards, GuideFilters } from './src/components/landing_page'; +export type { GuideFilterValues } from './src/components/landing_page'; export { testGuideId, testGuideConfig } from './src/common/test_guide_config'; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card.test.tsx.snap deleted file mode 100644 index b286cba768f03..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card.test.tsx.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`guide card snapshots should render use case card component for kubernetes 1`] = ` - - } - isDarkTheme={false} - title="Observe my Kubernetes infrastructure" - useCase="kubernetes" -/> -`; - -exports[`guide card snapshots should render use case card component for search 1`] = ` - - } - isDarkTheme={false} - title="Search my data" - useCase="search" -/> -`; - -exports[`guide card snapshots should render use case card component for siem 1`] = ` - - } - isDarkTheme={false} - title="Protect my environment" - useCase="siem" -/> -`; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap deleted file mode 100644 index a5fe07d1a872c..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap +++ /dev/null @@ -1,181 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`guide card footer snapshots should render the footer when the guide has been completed 1`] = ` - - - - - - - View guide - - - - -`; - -exports[`guide card footer snapshots should render the footer when the guide has not started yet 1`] = ` - - - - View guide - - - -`; - -exports[`guide card footer snapshots should render the footer when the guide is in progress 1`] = ` - - - - - - - Continue - - - - -`; - -exports[`guide card footer snapshots should render the footer when the guide is ready to complete 1`] = ` - - - - - - - Continue - - - - -`; - -exports[`guide card footer snapshots should render the footer when the guided onboarding has not started yet 1`] = ` - - - - View guide - - - -`; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap new file mode 100644 index 0000000000000..796cd57b3396e --- /dev/null +++ b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_cards.test.tsx.snap @@ -0,0 +1,255 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`guide cards snapshots should render all cards 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/infrastructure_link_card.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/infrastructure_link_card.test.tsx.snap deleted file mode 100644 index e79ebcdeca5be..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/infrastructure_link_card.test.tsx.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`observability link card snapshots should render link card for observability 1`] = ` - - - - View integrations - - - - } - isDarkTheme={false} - title="Observe my data" - useCase="infrastructure" -/> -`; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.test.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.test.tsx deleted file mode 100644 index c078c5ce8f170..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.test.tsx +++ /dev/null @@ -1,40 +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 React from 'react'; -import { shallow } from 'enzyme'; - -import { GuideCard, GuideCardProps } from './guide_card'; - -const defaultProps: GuideCardProps = { - useCase: 'search', - guides: [], - activateGuide: jest.fn(), - isDarkTheme: false, - addBasePath: jest.fn(), -}; - -describe('guide card', () => { - describe('snapshots', () => { - test('should render use case card component for search', async () => { - const component = await shallow(); - - expect(component).toMatchSnapshot(); - }); - test('should render use case card component for kubernetes', async () => { - const component = await shallow(); - - expect(component).toMatchSnapshot(); - }); - test('should render use case card component for siem', async () => { - const component = await shallow(); - - expect(component).toMatchSnapshot(); - }); - }); -}); diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx index 1fed74c7ec346..a6f1ea67bb50b 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card.tsx @@ -6,108 +6,115 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useCallback, useState } from 'react'; +import { css } from '@emotion/react'; + +import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiTextColor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { GuideState } from '../../types'; -import { GuideCardFooter } from './guide_card_footer'; -import { UseCaseCard } from './use_case_card'; +import { GuideCardConstants } from './guide_cards.constants'; +import { GuideCardsProps } from './guide_cards'; -// separate type for GuideCardUseCase that includes some of GuideIds -export type GuideCardUseCase = 'search' | 'kubernetes' | 'siem'; -type GuideCardConstants = { - [key in GuideCardUseCase]: { - i18nTexts: { - title: string; - description: string; - }; - // duplicate the telemetry id from the guide config to not load the config from the endpoint - // this might change if we decide to use the guide config for the cards - // see this issue https://github.com/elastic/kibana/issues/146672 - telemetryId: string; - }; -}; +const cardCss = css` + position: relative; + min-height: 110px; + width: 380px; + .euiCard__content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } +`; -const constants: GuideCardConstants = { - search: { - i18nTexts: { - title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle', { - defaultMessage: 'Search my data', - }), - description: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription', - { - defaultMessage: - 'Create a search experience for your websites, applications, workplace content, or anything in between.', - } - ), - }, - telemetryId: 'search', - }, - kubernetes: { - i18nTexts: { - title: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.kubernetes.cardTitle', - { - defaultMessage: 'Observe my Kubernetes infrastructure', - } - ), - description: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.kubernetes.cardDescription', - { - defaultMessage: - 'Monitor your Kubernetes infrastructure by consolidating your logs and metrics.', - } - ), - }, - telemetryId: 'kubernetes', - }, - siem: { - i18nTexts: { - title: i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.siem.cardTitle', { - defaultMessage: 'Protect my environment', - }), - description: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.siem.cardDescription', - { - defaultMessage: - 'Investigate threats and get your SIEM up and running by installing the Elastic Defend integration.', - } - ), +const getProgressLabel = (guideState: GuideState | undefined): string | undefined => { + if (!guideState) { + return undefined; + } + const { steps } = guideState; + const numberSteps = steps.length; + const numberCompleteSteps = steps.filter((step) => step.status === 'complete').length; + if (numberCompleteSteps < 1 || numberCompleteSteps === numberSteps) { + return undefined; + } + return i18n.translate('guidedOnboardingPackage.gettingStarted.cards.progressLabel', { + defaultMessage: '{numberCompleteSteps} of {numberSteps} steps complete', + values: { + numberCompleteSteps, + numberSteps, }, - telemetryId: 'siem', - }, + }); }; -export interface GuideCardProps { - useCase: GuideCardUseCase; - guides: GuideState[]; - activateGuide: (useCase: GuideCardUseCase, guide?: GuideState) => Promise; - isDarkTheme: boolean; - addBasePath: (url: string) => string; -} export const GuideCard = ({ - useCase, - guides, + card, + guidesState, activateGuide, - isDarkTheme, - addBasePath, -}: GuideCardProps) => { + navigateToApp, + activeFilter, +}: GuideCardsProps & { card: GuideCardConstants }) => { + const [isLoading, setIsLoading] = useState(false); + let guideState: GuideState | undefined; + if (card.guideId) { + guideState = guidesState.find((state) => state.guideId === card.guideId); + } + + const onClick = useCallback(async () => { + setIsLoading(true); + if (card.guideId) { + await activateGuide(card.guideId, guideState); + } else if (card.navigateTo) { + await navigateToApp(card.navigateTo?.appId, { + path: card.navigateTo.path, + }); + } + setIsLoading(false); + }, [activateGuide, card.guideId, card.navigateTo, guideState, navigateToApp]); + + const isHighlighted = activeFilter === 'all' || activeFilter === card.solution; + const isComplete = guideState && guideState.status === 'complete'; + const progress = getProgressLabel(guideState); return ( - + + +

{card.title}

+ + } + titleSize="xs" + betaBadgeProps={{ + label: card.solution, + }} + description={ + <> + {progress && ( + + {progress} + + )} + {isComplete && ( + + + + + + + {i18n.translate('guidedOnboardingPackage.gettingStarted.cards.completeLabel', { + defaultMessage: 'Guide complete', + })} + + + + )} + } - isDarkTheme={isDarkTheme} - addBasePath={addBasePath} /> ); }; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.test.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.test.tsx deleted file mode 100644 index d70316c7604f7..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.test.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 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 React from 'react'; -import { shallow } from 'enzyme'; - -import { GuideCardFooter, GuideCardFooterProps } from './guide_card_footer'; -import { GuideState } from '../../types'; - -const defaultProps: GuideCardFooterProps = { - guides: [], - useCase: 'search', - telemetryId: 'search', - activateGuide: jest.fn(), -}; - -const searchGuideState: GuideState = { - guideId: 'search', - status: 'not_started', - steps: [ - { id: 'add_data', status: 'complete' }, - { id: 'search_experience', status: 'in_progress' }, - ], - isActive: true, -}; -describe('guide card footer', () => { - describe('snapshots', () => { - test('should render the footer when the guided onboarding has not started yet', async () => { - const component = await shallow(); - expect(component).toMatchSnapshot(); - }); - - test('should render the footer when the guide has not started yet', async () => { - const component = await shallow( - - ); - expect(component).toMatchSnapshot(); - }); - - test('should render the footer when the guide is in progress', async () => { - const component = await shallow( - - ); - expect(component).toMatchSnapshot(); - }); - - test('should render the footer when the guide is ready to complete', async () => { - const component = await shallow( - - ); - expect(component).toMatchSnapshot(); - }); - - test('should render the footer when the guide has been completed', async () => { - const component = await shallow( - - ); - expect(component).toMatchSnapshot(); - }); - }); -}); diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx deleted file mode 100644 index 9646870782ff2..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx +++ /dev/null @@ -1,144 +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 React, { useState, useCallback } from 'react'; -import { css } from '@emotion/react'; -import { EuiButton, EuiProgress, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { GuideId, GuideState } from '../../types'; -import type { GuideCardUseCase } from './guide_card'; - -const viewGuideLabel = i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel', - { - defaultMessage: 'View guide', - } -); - -const continueGuideLabel = i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel', - { - defaultMessage: 'Continue', - } -); - -const completedLabel = i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel', - { - defaultMessage: 'Completed', - } -); - -const inProgressLabel = i18n.translate( - 'guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel', - { - defaultMessage: 'In progress', - } -); - -// The progress bar is rendered within EuiCard, which centers content by default -const progressBarLabelCss = css` - text-align: 'left'; -`; - -export interface GuideCardFooterProps { - guides: GuideState[]; - useCase: GuideCardUseCase; - telemetryId: string; - activateGuide: (useCase: GuideCardUseCase, guideState?: GuideState) => Promise; -} -export const GuideCardFooter = ({ - guides, - useCase, - telemetryId, - activateGuide, -}: GuideCardFooterProps) => { - const guideState = guides.find((guide) => guide.guideId === (useCase as GuideId)); - const [isLoading, setIsLoading] = useState(false); - const activateGuideCallback = useCallback(async () => { - setIsLoading(true); - await activateGuide(useCase, guideState); - setIsLoading(false); - }, [activateGuide, guideState, useCase]); - const viewGuideButton = ( - - - - {viewGuideLabel} - - - - ); - // guide has not started yet - if (!guideState || guideState.status === 'not_started') { - return viewGuideButton; - } - const { status, steps } = guideState; - const numberSteps = steps.length; - const numberCompleteSteps = steps.filter((step) => step.status === 'complete').length; - const stepsLabel = i18n.translate('guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel', { - defaultMessage: '{progress} steps', - values: { - progress: `${numberCompleteSteps}/${numberSteps}`, - }, - }); - // guide is completed - if (status === 'complete') { - return ( - <> - - - {viewGuideButton} - - ); - } - // guide is in progress or ready to complete - return ( - <> - - - - - - {continueGuideLabel} - - - - - ); -}; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.ts b/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.ts new file mode 100644 index 0000000000000..889a17b660e24 --- /dev/null +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.constants.ts @@ -0,0 +1,139 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { GuideId } from '../../..'; +import { GuideCardSolutions } from './guide_cards'; + +export interface GuideCardConstants { + solution: GuideCardSolutions; + title: string; + // if present, guideId indicates which guide is opened when clicking the card + guideId?: GuideId; + // if present, navigateTo indicates where the user will be redirected, when clicking the card + navigateTo?: { + appId: string; + path?: string; + }; + // duplicate the telemetry id from the guide config to not load the config from the endpoint + // this might change if we decide to use the guide config for the cards + // see this issue https://github.com/elastic/kibana/issues/146672 + telemetryId: string; + order: number; +} + +export const guideCards: GuideCardConstants[] = [ + { + solution: 'search', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.appSearch.title', { + defaultMessage: 'Build an application on top of Elasticsearch', + }), + guideId: 'search', + telemetryId: 'guided-onboarding--search--application', + order: 1, + }, + { + solution: 'search', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.websiteSearch.title', { + defaultMessage: 'Add search to my website', + }), + guideId: 'search', + telemetryId: 'guided-onboarding--search--website', + order: 4, + }, + { + solution: 'search', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.databaseSearch.title', { + defaultMessage: 'Search across databases and business systems', + }), + guideId: 'search', + telemetryId: 'guided-onboarding--search--database', + order: 7, + }, + { + solution: 'observability', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.logsObservability.title', { + defaultMessage: 'Collect and analyze my logs', + }), + navigateTo: { + appId: 'integrations', + path: '/browse?q=log', + }, + telemetryId: 'guided-onboarding--observability--logs', + order: 2, + }, + { + solution: 'observability', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.apmObservability.title', { + defaultMessage: 'Monitor my application performance (APM / tracing)', + }), + navigateTo: { + appId: 'home', + path: '#/tutorial/apm', + }, + telemetryId: 'guided-onboarding--observability--apm', + order: 5, + }, + { + solution: 'observability', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.hostsObservability.title', { + defaultMessage: 'Monitor my host metrics', + }), + navigateTo: { + appId: 'integrations', + path: '/browse/os_system', + }, + telemetryId: 'guided-onboarding--observability--hosts', + order: 8, + }, + { + solution: 'observability', + title: i18n.translate( + 'guidedOnboardingPackage.gettingStarted.cards.kubernetesObservability.title', + { + defaultMessage: 'Monitor Kubernetes clusters', + } + ), + guideId: 'kubernetes', + telemetryId: 'guided-onboarding--observability--kubernetes', + order: 11, + }, + { + solution: 'security', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.siemSecurity.title', { + defaultMessage: 'Detect threats in my data with SIEM', + }), + guideId: 'siem', + telemetryId: 'guided-onboarding--security--siem', + order: 3, + }, + { + solution: 'security', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.hostsSecurity.title', { + defaultMessage: 'Secure my hosts with endpoint security', + }), + navigateTo: { + appId: 'integrations', + path: '/detail/endpoint/overview', + }, + telemetryId: 'guided-onboarding--security--hosts', + order: 6, + }, + { + solution: 'security', + title: i18n.translate('guidedOnboardingPackage.gettingStarted.cards.cloudSecurity.title', { + defaultMessage: 'Secure my cloud assets with posture management', + }), + navigateTo: { + appId: 'integrations', + path: '/detail/cloud_security_posture/overview', + }, + telemetryId: 'guided-onboarding--security--cloud', + order: 9, + }, +].sort((cardA, cardB) => cardA.order - cardB.order) as GuideCardConstants[]; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.test.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.test.tsx similarity index 61% rename from packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.test.tsx rename to packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.test.tsx index d1ac3c20c6c6d..de14e79a3b10a 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.test.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.test.tsx @@ -8,18 +8,20 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { InfrastructureLinkCard } from './infrastructure_link_card'; -const defaultProps = { +import { GuideCards, GuideCardsProps } from './guide_cards'; + +const defaultProps: GuideCardsProps = { + activateGuide: jest.fn(), navigateToApp: jest.fn(), - isDarkTheme: false, - addBasePath: jest.fn(), + activeFilter: 'all', + guidesState: [], }; -describe('observability link card', () => { +describe('guide cards', () => { describe('snapshots', () => { - test('should render link card for observability', async () => { - const component = await shallow(); + test('should render all cards', async () => { + const component = await shallow(); expect(component).toMatchSnapshot(); }); }); diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.tsx new file mode 100644 index 0000000000000..45d32d8089ef3 --- /dev/null +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide_cards.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 React from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +import { ApplicationStart } from '@kbn/core-application-browser'; + +import { GuideId, GuideState } from '../../types'; +import { GuideFilterValues } from './guide_filters'; +import { guideCards } from './guide_cards.constants'; +import { GuideCard } from './guide_card'; + +export type GuideCardSolutions = 'search' | 'observability' | 'security'; + +export interface GuideCardsProps { + activateGuide: (guideId: GuideId, guideState?: GuideState) => Promise; + navigateToApp: ApplicationStart['navigateToApp']; + activeFilter: GuideFilterValues; + guidesState: GuideState[]; +} +export const GuideCards = (props: GuideCardsProps) => { + return ( + + {guideCards.map((card, index) => ( + + + + + ))} + + ); +}; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_filters.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_filters.tsx new file mode 100644 index 0000000000000..7dd1641a0ee4d --- /dev/null +++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide_filters.tsx @@ -0,0 +1,93 @@ +/* + * 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 React from 'react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { GuideCardSolutions } from './guide_cards'; + +const filterButtonCss = css` + border-radius: 20px !important; + min-width: 0 !important; + padding: 0 18px !important; + height: 32px !important; + &:hover { + text-decoration: none !important; + transform: none !important; + transition: none !important; + } + &:focus { + text-decoration: none; + } +`; +export type GuideFilterValues = GuideCardSolutions | 'all'; +interface GuideFiltersProps { + activeFilter: GuideFilterValues; + setActiveFilter: React.Dispatch>; +} +export const GuideFilters = ({ activeFilter, setActiveFilter }: GuideFiltersProps) => { + const { euiTheme } = useEuiTheme(); + const activeFilterFill = css` + background: ${euiTheme.colors.darkestShade}; + color: ${euiTheme.colors.lightestShade}; + `; + + return ( + + + setActiveFilter('all')} + color="text" + css={[filterButtonCss, activeFilter === 'all' && activeFilterFill]} + > + + + + + setActiveFilter('search')} + color="text" + css={[filterButtonCss, activeFilter === 'search' && activeFilterFill]} + > + + + + + setActiveFilter('observability')} + color="text" + css={[filterButtonCss, activeFilter === 'observability' && activeFilterFill]} + > + + + + + setActiveFilter('security')} + color="text" + css={[filterButtonCss, activeFilter === 'security' && activeFilterFill]} + > + + + + + ); +}; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/index.ts b/packages/kbn-guided-onboarding/src/components/landing_page/index.ts index db466a89e18dc..ee7933dd8fd4a 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/index.ts +++ b/packages/kbn-guided-onboarding/src/components/landing_page/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export { GuideCard } from './guide_card'; -export type { GuideCardUseCase } from './guide_card'; -export { InfrastructureLinkCard } from './infrastructure_link_card'; -export type { UseCase } from './use_case_card'; +export { GuideCards } from './guide_cards'; +export { GuideFilters } from './guide_filters'; +export type { GuideFilterValues } from './guide_filters'; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx deleted file mode 100644 index 34dc3910ed125..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/infrastructure_link_card.tsx +++ /dev/null @@ -1,87 +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 React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { NavigateToAppOptions } from '@kbn/core-application-browser'; -import { UseCaseCard } from './use_case_card'; - -interface LinkCardConstants { - infrastructure: { - i18nTexts: { - title: string; - description: string; - }; - }; -} - -const constants: LinkCardConstants = { - infrastructure: { - i18nTexts: { - title: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.cardTitle', - { - defaultMessage: 'Observe my data', - } - ), - description: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.cardDescription', - { - defaultMessage: - 'Add application, infrastructure, and user data through our pre-built integrations.', - } - ), - }, - }, -}; - -export const InfrastructureLinkCard = ({ - navigateToApp, - isDarkTheme, - addBasePath, -}: { - navigateToApp: (appId: string, options?: NavigateToAppOptions) => Promise; - isDarkTheme: boolean; - addBasePath: (url: string) => string; -}) => { - const navigateToIntegrations = () => { - navigateToApp('integrations', { - path: '/browse/infrastructure', - }); - }; - const button = ( - - - - {i18n.translate( - 'guidedOnboardingPackage.gettingStarted.infrastructure.linkCard.buttonLabel', - { - defaultMessage: 'View integrations', - } - )} - - - - ); - return ( - - ); -}; diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx deleted file mode 100644 index d726bf624d6fe..0000000000000 --- a/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx +++ /dev/null @@ -1,117 +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 React, { ReactNode } from 'react'; -import { EuiCard, EuiText, EuiImage } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import type { GuideCardUseCase } from './guide_card'; - -type UseCaseConstants = { - [key in UseCase]: { - logAltText: string; - betaBadgeLabel: string; - imageUrlPrefix: string; - }; -}; -const constants: UseCaseConstants = { - search: { - logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.search.iconName', { - defaultMessage: 'Enterprise Search logo', - }), - betaBadgeLabel: i18n.translate('guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel', { - defaultMessage: 'search', - }), - imageUrlPrefix: '/plugins/home/assets/solution_logos/search', - }, - kubernetes: { - logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.kubernetes.iconName', { - defaultMessage: 'Observability logo', - }), - betaBadgeLabel: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.kubernetes.betaBadgeLabel', - { - defaultMessage: 'observe', - } - ), - imageUrlPrefix: '/plugins/home/assets/solution_logos/kubernetes', - }, - infrastructure: { - logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.infrastructure.iconName', { - defaultMessage: 'Observability logo', - }), - betaBadgeLabel: i18n.translate( - 'guidedOnboardingPackage.gettingStarted.infrastructure.betaBadgeLabel', - { - defaultMessage: 'observe', - } - ), - imageUrlPrefix: '/plugins/home/assets/solution_logos/observability', - }, - siem: { - logAltText: i18n.translate('guidedOnboardingPackage.gettingStarted.siem.iconName', { - defaultMessage: 'Security logo', - }), - betaBadgeLabel: i18n.translate('guidedOnboardingPackage.gettingStarted.siem.betaBadgeLabel', { - defaultMessage: 'protect', - }), - imageUrlPrefix: '/plugins/home/assets/solution_logos/security', - }, -}; - -export type UseCase = GuideCardUseCase | 'infrastructure'; - -export interface UseCaseCardProps { - useCase: UseCase; - title: string; - description: string; - footer: ReactNode; - isDarkTheme: boolean; - addBasePath: (url: string) => string; -} - -export const UseCaseCard = ({ - useCase, - title, - description, - footer, - isDarkTheme, - addBasePath, -}: UseCaseCardProps) => { - const getImageUrl = (imageUrlPrefix: string) => { - const imagePath = `${imageUrlPrefix}${isDarkTheme ? '_dark' : ''}.png`; - return addBasePath(imagePath); - }; - - const titleElement = ( - -

- {title} -

-
- ); - - return ( - - } - title={titleElement} - description={description} - footer={footer} - paddingSize="l" - betaBadgeProps={{ - label: constants[useCase].betaBadgeLabel, - }} - /> - ); -}; diff --git a/packages/kbn-guided-onboarding/tsconfig.json b/packages/kbn-guided-onboarding/tsconfig.json index 5f0ba6f1c54f6..e1a8f2ab96b92 100644 --- a/packages/kbn-guided-onboarding/tsconfig.json +++ b/packages/kbn-guided-onboarding/tsconfig.json @@ -13,7 +13,8 @@ ], "kbn_references": [ "@kbn/i18n", - "@kbn/core-application-browser" + "@kbn/core-application-browser", + "@kbn/i18n-react" ], "exclude": [ "target/**/*", diff --git a/packages/kbn-handlebars/.gitignore b/packages/kbn-handlebars/.gitignore deleted file mode 100644 index d36977dc47615..0000000000000 --- a/packages/kbn-handlebars/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.tmp diff --git a/packages/kbn-handlebars/.patches/basic.patch b/packages/kbn-handlebars/.patches/basic.patch deleted file mode 100644 index e41c3b1cc9a85..0000000000000 --- a/packages/kbn-handlebars/.patches/basic.patch +++ /dev/null @@ -1,608 +0,0 @@ -1c1,6 -< global.handlebarsEnv = null; ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -3,5c8,9 -< beforeEach(function() { -< global.handlebarsEnv = Handlebars.create(); -< }); ---- -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -7,11c11,13 -< describe('basic context', function() { -< it('most basic', function() { -< expectTemplate('{{foo}}') -< .withInput({ foo: 'foo' }) -< .toCompileTo('foo'); ---- -> describe('basic context', () => { -> it('most basic', () => { -> expectTemplate('{{foo}}').withInput({ foo: 'foo' }).toCompileTo('foo'); -14,33c16,21 -< it('escaping', function() { -< expectTemplate('\\{{foo}}') -< .withInput({ foo: 'food' }) -< .toCompileTo('{{foo}}'); -< -< expectTemplate('content \\{{foo}}') -< .withInput({ foo: 'food' }) -< .toCompileTo('content {{foo}}'); -< -< expectTemplate('\\\\{{foo}}') -< .withInput({ foo: 'food' }) -< .toCompileTo('\\food'); -< -< expectTemplate('content \\\\{{foo}}') -< .withInput({ foo: 'food' }) -< .toCompileTo('content \\food'); -< -< expectTemplate('\\\\ {{foo}}') -< .withInput({ foo: 'food' }) -< .toCompileTo('\\\\ food'); ---- -> it('escaping', () => { -> expectTemplate('\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('{{foo}}'); -> expectTemplate('content \\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content {{foo}}'); -> expectTemplate('\\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('\\food'); -> expectTemplate('content \\\\{{foo}}').withInput({ foo: 'food' }).toCompileTo('content \\food'); -> expectTemplate('\\\\ {{foo}}').withInput({ foo: 'food' }).toCompileTo('\\\\ food'); -36c24 -< it('compiling with a basic context', function() { ---- -> it('compiling with a basic context', () => { -40c28 -< world: 'world' ---- -> world: 'world', -42d29 -< .withMessage('It works if all the required keys are provided') -46,49c33,34 -< it('compiling with a string context', function() { -< expectTemplate('{{.}}{{length}}') -< .withInput('bye') -< .toCompileTo('bye3'); ---- -> it('compiling with a string context', () => { -> expectTemplate('{{.}}{{length}}').withInput('bye').toCompileTo('bye3'); -52c37 -< it('compiling with an undefined context', function() { ---- -> it('compiling with an undefined context', () => { -62c47 -< it('comments', function() { ---- -> it('comments', () => { -66c51 -< world: 'world' ---- -> world: 'world', -68d52 -< .withMessage('comments are ignored') -72,76c56 -< -< expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo( -< 'blah' -< ); -< ---- -> expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo('blah'); -78,82c58 -< -< expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo( -< ' blah' -< ); -< ---- -> expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(' blah'); -84,87c60 -< -< expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo( -< ' blah' -< ); ---- -> expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(' blah'); -90,91c63,64 -< it('boolean', function() { -< var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; ---- -> it('boolean', () => { -> const string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!'; -95c68 -< world: 'world' ---- -> world: 'world', -97d69 -< .withMessage('booleans show the contents when true') -103c75 -< world: 'world' ---- -> world: 'world', -105d76 -< .withMessage('booleans do not show the contents when false') -109c80 -< it('zeros', function() { ---- -> it('zeros', () => { -113c84 -< num2: 0 ---- -> num2: 0, -117,119c88 -< expectTemplate('num: {{.}}') -< .withInput(0) -< .toCompileTo('num: 0'); ---- -> expectTemplate('num: {{.}}').withInput(0).toCompileTo('num: 0'); -126c95 -< it('false', function() { ---- -> it('false', () => { -131c100 -< val2: new Boolean(false) ---- -> val2: new Boolean(false), -135,137c104 -< expectTemplate('val: {{.}}') -< .withInput(false) -< .toCompileTo('val: false'); ---- -> expectTemplate('val: {{.}}').withInput(false).toCompileTo('val: false'); -146c113 -< val2: new Boolean(false) ---- -> val2: new Boolean(false), -156c123 -< it('should handle undefined and null', function() { ---- -> it('should handle undefined and null', () => { -159,167c126,128 -< awesome: function(_undefined, _null, options) { -< return ( -< (_undefined === undefined) + -< ' ' + -< (_null === null) + -< ' ' + -< typeof options -< ); -< } ---- -> awesome(_undefined: any, _null: any, options: any) { -> return (_undefined === undefined) + ' ' + (_null === null) + ' ' + typeof options; -> }, -173c134 -< undefined: function() { ---- -> undefined() { -175c136 -< } ---- -> }, -181c142 -< null: function() { ---- -> null() { -183c144 -< } ---- -> }, -188c149 -< it('newlines', function() { ---- -> it('newlines', () => { -190d150 -< -194,216c154,159 -< it('escaping text', function() { -< expectTemplate("Awesome's") -< .withMessage( -< "text is escaped so that it doesn't get caught on single quotes" -< ) -< .toCompileTo("Awesome's"); -< -< expectTemplate('Awesome\\') -< .withMessage("text is escaped so that the closing quote can't be ignored") -< .toCompileTo('Awesome\\'); -< -< expectTemplate('Awesome\\\\ foo') -< .withMessage("text is escaped so that it doesn't mess up backslashes") -< .toCompileTo('Awesome\\\\ foo'); -< -< expectTemplate('Awesome {{foo}}') -< .withInput({ foo: '\\' }) -< .withMessage("text is escaped so that it doesn't mess up backslashes") -< .toCompileTo('Awesome \\'); -< -< expectTemplate(" ' ' ") -< .withMessage('double quotes never produce invalid javascript') -< .toCompileTo(" ' ' "); ---- -> it('escaping text', () => { -> expectTemplate("Awesome's").toCompileTo("Awesome's"); -> expectTemplate('Awesome\\').toCompileTo('Awesome\\'); -> expectTemplate('Awesome\\\\ foo').toCompileTo('Awesome\\\\ foo'); -> expectTemplate('Awesome {{foo}}').withInput({ foo: '\\' }).toCompileTo('Awesome \\'); -> expectTemplate(" ' ' ").toCompileTo(" ' ' "); -219,223c162,163 -< it('escaping expressions', function() { -< expectTemplate('{{{awesome}}}') -< .withInput({ awesome: "&'\\<>" }) -< .withMessage("expressions with 3 handlebars aren't escaped") -< .toCompileTo("&'\\<>"); ---- -> it('escaping expressions', () => { -> expectTemplate('{{{awesome}}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); -225,228c165 -< expectTemplate('{{&awesome}}') -< .withInput({ awesome: "&'\\<>" }) -< .withMessage("expressions with {{& handlebars aren't escaped") -< .toCompileTo("&'\\<>"); ---- -> expectTemplate('{{&awesome}}').withInput({ awesome: "&'\\<>" }).toCompileTo("&'\\<>"); -232d168 -< .withMessage('by default expressions should be escaped') -237d172 -< .withMessage('escaping should properly handle amperstands') -241c176 -< it("functions returning safestrings shouldn't be escaped", function() { ---- -> it("functions returning safestrings shouldn't be escaped", () => { -244c179 -< awesome: function() { ---- -> awesome() { -246c181 -< } ---- -> }, -248d182 -< .withMessage("functions returning safestrings aren't escaped") -252c186 -< it('functions', function() { ---- -> it('functions', () => { -255c189 -< awesome: function() { ---- -> awesome() { -257c191 -< } ---- -> }, -259d192 -< .withMessage('functions are called and render their output') -264c197 -< awesome: function() { ---- -> awesome() { -267c200 -< more: 'More awesome' ---- -> more: 'More awesome', -269d201 -< .withMessage('functions are bound to the context') -273c205 -< it('functions with context argument', function() { ---- -> it('functions with context argument', () => { -276c208 -< awesome: function(context) { ---- -> awesome(context: any) { -279c211 -< frank: 'Frank' ---- -> frank: 'Frank', -281d212 -< .withMessage('functions are called with context arguments') -285c216 -< it('pathed functions with context argument', function() { ---- -> it('pathed functions with context argument', () => { -289c220 -< awesome: function(context) { ---- -> awesome(context: any) { -291c222 -< } ---- -> }, -293c224 -< frank: 'Frank' ---- -> frank: 'Frank', -295d225 -< .withMessage('functions are called with context arguments') -299c229 -< it('depthed functions with context argument', function() { ---- -> it('depthed functions with context argument', () => { -302c232 -< awesome: function(context) { ---- -> awesome(context: any) { -305c235 -< frank: 'Frank' ---- -> frank: 'Frank', -307d236 -< .withMessage('functions are called with context arguments') -311c240 -< it('block functions with context argument', function() { ---- -> it('block functions with context argument', () => { -314c243 -< awesome: function(context, options) { ---- -> awesome(context: any, options: any) { -316c245 -< } ---- -> }, -318d246 -< .withMessage('block functions are called with context and options') -322,325c250,251 -< it('depthed block functions with context argument', function() { -< expectTemplate( -< '{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}' -< ) ---- -> it('depthed block functions with context argument', () => { -> expectTemplate('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}') -328c254 -< awesome: function(context, options) { ---- -> awesome(context: any, options: any) { -330c256 -< } ---- -> }, -332d257 -< .withMessage('block functions are called with context and options') -336c261 -< it('block functions without context argument', function() { ---- -> it('block functions without context argument', () => { -339c264 -< awesome: function(options) { ---- -> awesome(options: any) { -341c266 -< } ---- -> }, -343d267 -< .withMessage('block functions are called with options') -347c271 -< it('pathed block functions without context argument', function() { ---- -> it('pathed block functions without context argument', () => { -351c275 -< awesome: function() { ---- -> awesome() { -353,354c277,278 -< } -< } ---- -> }, -> }, -356d279 -< .withMessage('block functions are called with options') -360,363c283,284 -< it('depthed block functions without context argument', function() { -< expectTemplate( -< '{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}' -< ) ---- -> it('depthed block functions without context argument', () => { -> expectTemplate('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}') -366c287 -< awesome: function() { ---- -> awesome() { -368c289 -< } ---- -> }, -370d290 -< .withMessage('block functions are called with options') -374,378c294,295 -< it('paths with hyphens', function() { -< expectTemplate('{{foo-bar}}') -< .withInput({ 'foo-bar': 'baz' }) -< .withMessage('Paths can contain hyphens (-)') -< .toCompileTo('baz'); ---- -> it('paths with hyphens', () => { -> expectTemplate('{{foo-bar}}').withInput({ 'foo-bar': 'baz' }).toCompileTo('baz'); -382d298 -< .withMessage('Paths can contain hyphens (-)') -387d302 -< .withMessage('Paths can contain hyphens (-)') -391c306 -< it('nested paths', function() { ---- -> it('nested paths', () => { -394d308 -< .withMessage('Nested paths access nested objects') -398c312 -< it('nested paths with empty string value', function() { ---- -> it('nested paths with empty string value', () => { -401d314 -< .withMessage('Nested paths access nested objects with empty string') -405c318 -< it('literal paths', function() { ---- -> it('literal paths', () => { -408d320 -< .withMessage('Literal paths can be used') -413d324 -< .withMessage('Literal paths can be used') -417c328 -< it('literal references', function() { ---- -> it('literal references', () => { -443c354 -< it("that current context path ({{.}}) doesn't hit helpers", function() { ---- -> it("that current context path ({{.}}) doesn't hit helpers", () => { -445a357 -> // @ts-expect-error Setting the helper to a string instead of a function doesn't make sense normally, but here it doesn't matter -450c362 -< it('complex but empty paths', function() { ---- -> it('complex but empty paths', () => { -455,457c367 -< expectTemplate('{{person/name}}') -< .withInput({ person: {} }) -< .toCompileTo(''); ---- -> expectTemplate('{{person/name}}').withInput({ person: {} }).toCompileTo(''); -460c370 -< it('this keyword in paths', function() { ---- -> it('this keyword in paths', () => { -463d372 -< .withMessage('This keyword in paths evaluates to current context') -468c377 -< hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] ---- -> hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], -470d378 -< .withMessage('This keyword evaluates in more complex paths') -474c382 -< it('this keyword nested inside path', function() { ---- -> it('this keyword nested inside path', () => { -476d383 -< Error, -480,482c387 -< expectTemplate('{{[this]}}') -< .withInput({ this: 'bar' }) -< .toCompileTo('bar'); ---- -> expectTemplate('{{[this]}}').withInput({ this: 'bar' }).toCompileTo('bar'); -489,491c394,396 -< it('this keyword in helpers', function() { -< var helpers = { -< foo: function(value) { ---- -> it('this keyword in helpers', () => { -> const helpers = { -> foo(value: any) { -493c398 -< } ---- -> }, -499d403 -< .withMessage('This keyword in paths evaluates to current context') -504c408 -< hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }] ---- -> hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }], -507d410 -< .withMessage('This keyword evaluates in more complex paths') -511c414 -< it('this keyword nested inside helpers param', function() { ---- -> it('this keyword nested inside helpers param', () => { -513d415 -< Error, -519c421 -< foo: function(value) { ---- -> foo(value: any) { -522c424 -< this: 'bar' ---- -> this: 'bar', -528c430 -< foo: function(value) { ---- -> foo(value: any) { -531c433 -< text: { this: 'bar' } ---- -> text: { this: 'bar' }, -536c438 -< it('pass string literals', function() { ---- -> it('pass string literals', () => { -538,541c440 -< -< expectTemplate('{{"foo"}}') -< .withInput({ foo: 'bar' }) -< .toCompileTo('bar'); ---- -> expectTemplate('{{"foo"}}').withInput({ foo: 'bar' }).toCompileTo('bar'); -545c444 -< foo: ['bar', 'baz'] ---- -> foo: ['bar', 'baz'], -550c449 -< it('pass number literals', function() { ---- -> it('pass number literals', () => { -552,556c451 -< -< expectTemplate('{{12}}') -< .withInput({ '12': 'bar' }) -< .toCompileTo('bar'); -< ---- -> expectTemplate('{{12}}').withInput({ '12': 'bar' }).toCompileTo('bar'); -558,562c453 -< -< expectTemplate('{{12.34}}') -< .withInput({ '12.34': 'bar' }) -< .toCompileTo('bar'); -< ---- -> expectTemplate('{{12.34}}').withInput({ '12.34': 'bar' }).toCompileTo('bar'); -565c456 -< '12.34': function(arg) { ---- -> '12.34'(arg: any) { -567c458 -< } ---- -> }, -572c463 -< it('pass boolean literals', function() { ---- -> it('pass boolean literals', () => { -574,581c465,466 -< -< expectTemplate('{{true}}') -< .withInput({ '': 'foo' }) -< .toCompileTo(''); -< -< expectTemplate('{{false}}') -< .withInput({ false: 'foo' }) -< .toCompileTo('foo'); ---- -> expectTemplate('{{true}}').withInput({ '': 'foo' }).toCompileTo(''); -> expectTemplate('{{false}}').withInput({ false: 'foo' }).toCompileTo('foo'); -584c469 -< it('should handle literals in subexpression', function() { ---- -> it('should handle literals in subexpression', () => { -587c472 -< false: function() { ---- -> false() { -589c474 -< } ---- -> }, -591c476 -< .withHelper('foo', function(arg) { ---- -> .withHelper('foo', function (arg) { diff --git a/packages/kbn-handlebars/.patches/blocks.patch b/packages/kbn-handlebars/.patches/blocks.patch deleted file mode 100644 index 11196151a164d..0000000000000 --- a/packages/kbn-handlebars/.patches/blocks.patch +++ /dev/null @@ -1,534 +0,0 @@ -1,3c1,13 -< describe('blocks', function() { -< it('array', function() { -< var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('blocks', () => { -> it('array', () => { -> const string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!'; -7,12c17,18 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -14d19 -< .withMessage('Arrays iterate over the contents when not empty') -20c25 -< world: 'world' ---- -> world: 'world', -22d26 -< .withMessage('Arrays ignore the contents when empty') -26,29c30,31 -< it('array without data', function() { -< expectTemplate( -< '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}' -< ) ---- -> it('array without data', () => { -> expectTemplate('{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}') -31,36c33,34 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -38d35 -< .withCompileOptions({ compat: false }) -42,45c39,40 -< it('array with @index', function() { -< expectTemplate( -< '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!' -< ) ---- -> it('array with @index', () => { -> expectTemplate('{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!') -47,52c42,43 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -54d44 -< .withMessage('The @index variable is used') -58,59c48,49 -< it('empty block', function() { -< var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; ---- -> it('empty block', () => { -> const string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!'; -63,68c53,54 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -70d55 -< .withMessage('Arrays iterate over the contents when not empty') -76c61 -< world: 'world' ---- -> world: 'world', -78d62 -< .withMessage('Arrays ignore the contents when empty') -82c66 -< it('block with complex lookup', function() { ---- -> it('block with complex lookup', () => { -86,90c70 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ] ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -92,97c72 -< .withMessage( -< 'Templates can access variables in contexts up the stack with relative path syntax' -< ) -< .toCompileTo( -< 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ' -< ); ---- -> .toCompileTo('goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '); -100c75 -< it('multiple blocks with complex lookup', function() { ---- -> it('multiple blocks with complex lookup', () => { -104,108c79 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ] ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -113,116c84,85 -< it('block with complex lookup using nested context', function() { -< expectTemplate( -< '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}' -< ).toThrow(Error); ---- -> it('block with complex lookup using nested context', () => { -> expectTemplate('{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}').toThrow(Error); -119c88 -< it('block with deep nested complex lookup', function() { ---- -> it('block with deep nested complex lookup', () => { -125c94 -< outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] ---- -> outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }], -130,133c99,100 -< it('works with cached blocks', function() { -< expectTemplate( -< '{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}' -< ) ---- -> it('works with cached blocks', () => { -> expectTemplate('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}') -138,139c105,106 -< { first: 'Alan', last: 'Johnson' } -< ] ---- -> { first: 'Alan', last: 'Johnson' }, -> ], -144,145c111,112 -< describe('inverted sections', function() { -< it('inverted sections with unset value', function() { ---- -> describe('inverted sections', () => { -> it('inverted sections with unset value', () => { -148,150c115 -< ) -< .withMessage("Inverted section rendered when value isn't set.") -< .toCompileTo('Right On!'); ---- -> ).toCompileTo('Right On!'); -153,156c118,119 -< it('inverted section with false value', function() { -< expectTemplate( -< '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' -< ) ---- -> it('inverted section with false value', () => { -> expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') -158d120 -< .withMessage('Inverted section rendered when value is false.') -162,165c124,125 -< it('inverted section with empty set', function() { -< expectTemplate( -< '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}' -< ) ---- -> it('inverted section with empty set', () => { -> expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}') -167d126 -< .withMessage('Inverted section rendered when value is empty set.') -171c130 -< it('block inverted sections', function() { ---- -> it('block inverted sections', () => { -177c136 -< it('chained inverted sections', function() { ---- -> it('chained inverted sections', () => { -188,190c147 -< expectTemplate( -< '{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}' -< ) ---- -> expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}') -195,198c152,153 -< it('chained inverted sections with mismatch', function() { -< expectTemplate( -< '{{#people}}{{name}}{{else if none}}{{none}}{{/if}}' -< ).toThrow(Error); ---- -> it('chained inverted sections with mismatch', () => { -> expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}').toThrow(Error); -201c156 -< it('block inverted sections with empty arrays', function() { ---- -> it('block inverted sections with empty arrays', () => { -205c160 -< people: [] ---- -> people: [], -211,212c166,167 -< describe('standalone sections', function() { -< it('block standalone else sections', function() { ---- -> describe('standalone sections', () => { -> it('block standalone else sections', () => { -226,241c181,182 -< it('block standalone else sections can be disabled', function() { -< expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n') -< .withInput({ none: 'No people' }) -< .withCompileOptions({ ignoreStandalone: true }) -< .toCompileTo('\nNo people\n\n'); -< -< expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n') -< .withInput({ none: 'No people' }) -< .withCompileOptions({ ignoreStandalone: true }) -< .toCompileTo('\nNo people\n\n'); -< }); -< -< it('block standalone chained else sections', function() { -< expectTemplate( -< '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n' -< ) ---- -> it('block standalone chained else sections', () => { -> expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n') -245,247c186 -< expectTemplate( -< '{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n' -< ) ---- -> expectTemplate('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n') -252c191 -< it('should handle nesting', function() { ---- -> it('should handle nesting', () => { -255c194 -< data: [1, 3, 5] ---- -> data: [1, 3, 5], -261,297c200,201 -< describe('compat mode', function() { -< it('block with deep recursive lookup lookup', function() { -< expectTemplate( -< '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}' -< ) -< .withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] }) -< .withCompileOptions({ compat: true }) -< .toCompileTo('Goodbye cruel OMG!'); -< }); -< -< it('block with deep recursive pathed lookup', function() { -< expectTemplate( -< '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' -< ) -< .withInput({ -< omg: { yes: 'OMG!' }, -< outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] -< }) -< .withCompileOptions({ compat: true }) -< .toCompileTo('Goodbye cruel OMG!'); -< }); -< -< it('block with missed recursive lookup', function() { -< expectTemplate( -< '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}' -< ) -< .withInput({ -< omg: { no: 'OMG!' }, -< outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] -< }) -< .withCompileOptions({ compat: true }) -< .toCompileTo('Goodbye cruel '); -< }); -< }); -< -< describe('decorators', function() { -< it('should apply mustache decorators', function() { ---- -> describe('decorators', () => { -> it('should apply mustache decorators', () => { -299,300c203,204 -< .withHelper('helper', function(options) { -< return options.fn.run; ---- -> .withHelper('helper', function (options: Handlebars.HelperOptions) { -> return (options.fn as any).run; -302,303c206,207 -< .withDecorator('decorator', function(fn) { -< fn.run = 'success'; ---- -> .withDecorator('decorator', function (fn) { -> (fn as any).run = 'success'; -309c213 -< it('should apply allow undefined return', function() { ---- -> it('should apply allow undefined return', () => { -311,312c215,216 -< .withHelper('helper', function(options) { -< return options.fn() + options.fn.run; ---- -> .withHelper('helper', function (options: Handlebars.HelperOptions) { -> return options.fn() + (options.fn as any).run; -314,315c218,219 -< .withDecorator('decorator', function(fn) { -< fn.run = 'cess'; ---- -> .withDecorator('decorator', function (fn) { -> (fn as any).run = 'cess'; -320,325c224,227 -< it('should apply block decorators', function() { -< expectTemplate( -< '{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}' -< ) -< .withHelper('helper', function(options) { -< return options.fn.run; ---- -> it('should apply block decorators', () => { -> expectTemplate('{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}') -> .withHelper('helper', function (options: Handlebars.HelperOptions) { -> return (options.fn as any).run; -327,328c229,230 -< .withDecorator('decorator', function(fn, props, container, options) { -< fn.run = options.fn(); ---- -> .withDecorator('decorator', function (fn, props, container, options) { -> (fn as any).run = options.fn(); -334c236 -< it('should support nested decorators', function() { ---- -> it('should support nested decorators', () => { -338,339c240,241 -< .withHelper('helper', function(options) { -< return options.fn.run; ---- -> .withHelper('helper', function (options: Handlebars.HelperOptions) { -> return (options.fn as any).run; -342,343c244,245 -< decorator: function(fn, props, container, options) { -< fn.run = options.fn.nested + options.fn(); ---- -> decorator(fn, props, container, options) { -> (fn as any).run = options.fn.nested + options.fn(); -346c248 -< nested: function(fn, props, container, options) { ---- -> nested(fn, props, container, options) { -348c250 -< } ---- -> }, -353c255 -< it('should apply multiple decorators', function() { ---- -> it('should apply multiple decorators', () => { -357,358c259,260 -< .withHelper('helper', function(options) { -< return options.fn.run; ---- -> .withHelper('helper', function (options: Handlebars.HelperOptions) { -> return (options.fn as any).run; -360,361c262,263 -< .withDecorator('decorator', function(fn, props, container, options) { -< fn.run = (fn.run || '') + options.fn(); ---- -> .withDecorator('decorator', function (fn, props, container, options) { -> (fn as any).run = ((fn as any).run || '') + options.fn(); -367c269 -< it('should access parent variables', function() { ---- -> it('should access parent variables', () => { -369,370c271,272 -< .withHelper('helper', function(options) { -< return options.fn.run; ---- -> .withHelper('helper', function (options: Handlebars.HelperOptions) { -> return (options.fn as any).run; -372,373c274,275 -< .withDecorator('decorator', function(fn, props, container, options) { -< fn.run = options.args; ---- -> .withDecorator('decorator', function (fn, props, container, options) { -> (fn as any).run = options.args; -380,381c282,283 -< it('should work with root program', function() { -< var run; ---- -> it('should work with root program', () => { -> let run; -383,384c285,286 -< .withDecorator('decorator', function(fn, props, container, options) { -< equals(options.args[0], 'success'); ---- -> .withDecorator('decorator', function (fn, props, container, options) { -> expect(options.args[0]).toEqual('success'); -390c292 -< equals(run, true); ---- -> expect(run).toEqual(true); -393,394c295,296 -< it('should fail when accessing variables from root', function() { -< var run; ---- -> it('should fail when accessing variables from root', () => { -> let run; -396,397c298,299 -< .withDecorator('decorator', function(fn, props, container, options) { -< equals(options.args[0], undefined); ---- -> .withDecorator('decorator', function (fn, props, container, options) { -> expect(options.args[0]).toBeUndefined(); -403c305 -< equals(run, true); ---- -> expect(run).toEqual(true); -406,408c308,311 -< describe('registration', function() { -< it('unregisters', function() { -< handlebarsEnv.decorators = {}; ---- -> describe('registration', () => { -> beforeEach(() => { -> global.kbnHandlebarsEnv = Handlebars.create(); -> }); -410c313,321 -< handlebarsEnv.registerDecorator('foo', function() { ---- -> afterEach(() => { -> global.kbnHandlebarsEnv = null; -> }); -> -> it('unregisters', () => { -> // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. -> kbnHandlebarsEnv!.decorators = {}; -> -> kbnHandlebarsEnv!.registerDecorator('foo', function () { -414,416c325,327 -< equals(!!handlebarsEnv.decorators.foo, true); -< handlebarsEnv.unregisterDecorator('foo'); -< equals(handlebarsEnv.decorators.foo, undefined); ---- -> expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); -> kbnHandlebarsEnv!.unregisterDecorator('foo'); -> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); -419,420c330,332 -< it('allows multiple globals', function() { -< handlebarsEnv.decorators = {}; ---- -> it('allows multiple globals', () => { -> // @ts-expect-error: Cannot assign to 'decorators' because it is a read-only property. -> kbnHandlebarsEnv!.decorators = {}; -422,424c334,337 -< handlebarsEnv.registerDecorator({ -< foo: function() {}, -< bar: function() {} ---- -> // @ts-expect-error: Expected 2 arguments, but got 1. -> kbnHandlebarsEnv!.registerDecorator({ -> foo() {}, -> bar() {}, -427,432c340,345 -< equals(!!handlebarsEnv.decorators.foo, true); -< equals(!!handlebarsEnv.decorators.bar, true); -< handlebarsEnv.unregisterDecorator('foo'); -< handlebarsEnv.unregisterDecorator('bar'); -< equals(handlebarsEnv.decorators.foo, undefined); -< equals(handlebarsEnv.decorators.bar, undefined); ---- -> expect(!!kbnHandlebarsEnv!.decorators.foo).toEqual(true); -> expect(!!kbnHandlebarsEnv!.decorators.bar).toEqual(true); -> kbnHandlebarsEnv!.unregisterDecorator('foo'); -> kbnHandlebarsEnv!.unregisterDecorator('bar'); -> expect(kbnHandlebarsEnv!.decorators.foo).toBeUndefined(); -> expect(kbnHandlebarsEnv!.decorators.bar).toBeUndefined(); -435,445c348,354 -< it('fails with multiple and args', function() { -< shouldThrow( -< function() { -< handlebarsEnv.registerDecorator( -< { -< world: function() { -< return 'world!'; -< }, -< testHelper: function() { -< return 'found it!'; -< } ---- -> it('fails with multiple and args', () => { -> expect(() => { -> kbnHandlebarsEnv!.registerDecorator( -> // @ts-expect-error: Argument of type '{ world(): string; testHelper(): string; }' is not assignable to parameter of type 'string'. -> { -> world() { -> return 'world!'; -447,452c356,362 -< {} -< ); -< }, -< Error, -< 'Arg not supported with multiple decorators' -< ); ---- -> testHelper() { -> return 'found it!'; -> }, -> }, -> {} -> ); -> }).toThrow('Arg not supported with multiple decorators'); diff --git a/packages/kbn-handlebars/.patches/builtins.patch b/packages/kbn-handlebars/.patches/builtins.patch deleted file mode 100644 index 30b40456c2142..0000000000000 --- a/packages/kbn-handlebars/.patches/builtins.patch +++ /dev/null @@ -1,937 +0,0 @@ -1,4c1,16 -< describe('builtin helpers', function() { -< describe('#if', function() { -< it('if', function() { -< var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> /* eslint-disable max-classes-per-file */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('builtin helpers', () => { -> describe('#if', () => { -> it('if', () => { -> const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; -9c21 -< world: 'world' ---- -> world: 'world', -11d22 -< .withMessage('if with boolean argument shows the contents when true') -17c28 -< world: 'world' ---- -> world: 'world', -19d29 -< .withMessage('if with string argument shows the contents') -25c35 -< world: 'world' ---- -> world: 'world', -27,29d36 -< .withMessage( -< 'if with boolean argument does not show the contents when false' -< ) -32,35c39 -< expectTemplate(string) -< .withInput({ world: 'world' }) -< .withMessage('if with undefined does not show the contents') -< .toCompileTo('cruel world!'); ---- -> expectTemplate(string).withInput({ world: 'world' }).toCompileTo('cruel world!'); -40c44 -< world: 'world' ---- -> world: 'world', -42d45 -< .withMessage('if with non-empty array shows the contents') -48c51 -< world: 'world' ---- -> world: 'world', -50d52 -< .withMessage('if with empty array does not show the contents') -56c58 -< world: 'world' ---- -> world: 'world', -58d59 -< .withMessage('if with zero does not show the contents') -61,63c62 -< expectTemplate( -< '{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!' -< ) ---- -> expectTemplate('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!') -66c65 -< world: 'world' ---- -> world: 'world', -68d66 -< .withMessage('if with zero does not show the contents') -72,73c70,71 -< it('if with function argument', function() { -< var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; ---- -> it('if with function argument', () => { -> const string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!'; -77c75 -< goodbye: function() { ---- -> goodbye() { -80c78 -< world: 'world' ---- -> world: 'world', -82,84d79 -< .withMessage( -< 'if with function shows the contents when function returns true' -< ) -89c84 -< goodbye: function() { ---- -> goodbye() { -92c87 -< world: 'world' ---- -> world: 'world', -94,96d88 -< .withMessage( -< 'if with function shows the contents when function returns string' -< ) -101c93 -< goodbye: function() { ---- -> goodbye() { -104c96 -< world: 'world' ---- -> world: 'world', -106,108d97 -< .withMessage( -< 'if with function does not show the contents when returns false' -< ) -113c102 -< goodbye: function() { ---- -> goodbye() { -116c105 -< world: 'world' ---- -> world: 'world', -118,120d106 -< .withMessage( -< 'if with function does not show the contents when returns undefined' -< ) -124,127c110,111 -< it('should not change the depth list', function() { -< expectTemplate( -< '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}' -< ) ---- -> it('should not change the depth list', () => { -> expectTemplate('{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}') -130c114 -< world: 'world' ---- -> world: 'world', -136,137c120,121 -< describe('#with', function() { -< it('with', function() { ---- -> describe('#with', () => { -> it('with', () => { -142,143c126,127 -< last: 'Johnson' -< } ---- -> last: 'Johnson', -> }, -148c132 -< it('with with function argument', function() { ---- -> it('with with function argument', () => { -151c135 -< person: function() { ---- -> person() { -154c138 -< last: 'Johnson' ---- -> last: 'Johnson', -156c140 -< } ---- -> }, -161c145 -< it('with with else', function() { ---- -> it('with with else', () => { -167c151 -< it('with provides block parameter', function() { ---- -> it('with provides block parameter', () => { -172,173c156,157 -< last: 'Johnson' -< } ---- -> last: 'Johnson', -> }, -178c162 -< it('works when data is disabled', function() { ---- -> it('works when data is disabled', () => { -186,194c170,172 -< describe('#each', function() { -< beforeEach(function() { -< handlebarsEnv.registerHelper('detectDataInsideEach', function(options) { -< return options.data && options.data.exclaim; -< }); -< }); -< -< it('each', function() { -< var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; ---- -> describe('#each', () => { -> it('each', () => { -> const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; -198,203c176,177 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -205,207d178 -< .withMessage( -< 'each with array argument iterates over the contents when not empty' -< ) -213c184 -< world: 'world' ---- -> world: 'world', -215d185 -< .withMessage('each with array argument ignores the contents when empty') -219c189 -< it('each without data', function() { ---- -> it('each without data', () => { -222,227c192,193 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -240c206 -< it('each without context', function() { ---- -> it('each without context', () => { -246,248c212,213 -< it('each with an object and @key', function() { -< var string = -< '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; ---- -> it('each with an object and @key', () => { -> const string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!'; -250c215 -< function Clazz() { ---- -> function Clazz(this: any) { -255c220 -< var hash = { goodbyes: new Clazz(), world: 'world' }; ---- -> const hash = { goodbyes: new (Clazz as any)(), world: 'world' }; -260,270c225,233 -< var actual = compileWithPartials(string, hash); -< var expected1 = -< '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!'; -< var expected2 = -< '2. GOODBYE! <b>#1</b>. goodbye! cruel world!'; -< -< equals( -< actual === expected1 || actual === expected2, -< true, -< 'each with object argument iterates over the contents when not empty' -< ); ---- -> try { -> expectTemplate(string) -> .withInput(hash) -> .toCompileTo('<b>#1</b>. goodbye! 2. GOODBYE! cruel world!'); -> } catch (e) { -> expectTemplate(string) -> .withInput(hash) -> .toCompileTo('2. GOODBYE! <b>#1</b>. goodbye! cruel world!'); -> } -275c238 -< world: 'world' ---- -> world: 'world', -280,283c243,244 -< it('each with @index', function() { -< expectTemplate( -< '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' -< ) ---- -> it('each with @index', () => { -> expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') -285,290c246,247 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -292d248 -< .withMessage('The @index variable is used') -296c252 -< it('each with nested @index', function() { ---- -> it('each with nested @index', () => { -301,306c257,258 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -308d259 -< .withMessage('The @index variable is used') -314c265 -< it('each with block params', function() { ---- -> it('each with block params', () => { -320c271 -< world: 'world' ---- -> world: 'world', -322,324c273 -< .toCompileTo( -< '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!' -< ); ---- -> .toCompileTo('0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'); -327,330c276,284 -< it('each with block params and strict compilation', function() { -< expectTemplate( -< '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}' -< ) ---- -> // TODO: This test has been added to the `4.x` branch of the handlebars.js repo along with a code-fix, -> // but a new version of the handlebars package containing this fix has not yet been published to npm. -> // -> // Before enabling this code, a new version of handlebars needs to be released and the corresponding -> // updates needs to be applied to this implementation. -> // -> // See: https://github.com/handlebars-lang/handlebars.js/commit/30dbf0478109ded8f12bb29832135d480c17e367 -> it.skip('each with block params and strict compilation', () => { -> expectTemplate('{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}') -336,339c290,291 -< it('each object with @index', function() { -< expectTemplate( -< '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' -< ) ---- -> it('each object with @index', () => { -> expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') -344c296 -< c: { text: 'GOODBYE' } ---- -> c: { text: 'GOODBYE' }, -346c298 -< world: 'world' ---- -> world: 'world', -348d299 -< .withMessage('The @index variable is used') -352,355c303,304 -< it('each with @first', function() { -< expectTemplate( -< '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' -< ) ---- -> it('each with @first', () => { -> expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -357,362c306,307 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -364d308 -< .withMessage('The @first variable is used') -368c312 -< it('each with nested @first', function() { ---- -> it('each with nested @first', () => { -373,378c317,318 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -380,383c320 -< .withMessage('The @first variable is used') -< .toCompileTo( -< '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!' -< ); ---- -> .toCompileTo('(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'); -386,389c323,324 -< it('each object with @first', function() { -< expectTemplate( -< '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' -< ) ---- -> it('each object with @first', () => { -> expectTemplate('{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -392c327 -< world: 'world' ---- -> world: 'world', -394d328 -< .withMessage('The @first variable is used') -398,401c332,333 -< it('each with @last', function() { -< expectTemplate( -< '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' -< ) ---- -> it('each with @last', () => { -> expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -403,408c335,336 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -410d337 -< .withMessage('The @last variable is used') -414,417c341,342 -< it('each object with @last', function() { -< expectTemplate( -< '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!' -< ) ---- -> it('each object with @last', () => { -> expectTemplate('{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!') -420c345 -< world: 'world' ---- -> world: 'world', -422d346 -< .withMessage('The @last variable is used') -426c350 -< it('each with nested @last', function() { ---- -> it('each with nested @last', () => { -431,436c355,356 -< goodbyes: [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ], -< world: 'world' ---- -> goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }], -> world: 'world', -438,441c358 -< .withMessage('The @last variable is used') -< .toCompileTo( -< '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!' -< ); ---- -> .toCompileTo('(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'); -444,445c361,362 -< it('each with function argument', function() { -< var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; ---- -> it('each with function argument', () => { -> const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; -449,454c366,367 -< goodbyes: function() { -< return [ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ]; ---- -> goodbyes() { -> return [{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]; -456c369 -< world: 'world' ---- -> world: 'world', -458,460d370 -< .withMessage( -< 'each with array function argument iterates over the contents when not empty' -< ) -466c376 -< world: 'world' ---- -> world: 'world', -468,470d377 -< .withMessage( -< 'each with array function argument ignores the contents when empty' -< ) -474,477c381,382 -< it('each object when last key is an empty string', function() { -< expectTemplate( -< '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!' -< ) ---- -> it('each object when last key is an empty string', () => { -> expectTemplate('{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!') -482c387 -< '': { text: 'GOODBYE' } ---- -> '': { text: 'GOODBYE' }, -484c389 -< world: 'world' ---- -> world: 'world', -486d390 -< .withMessage('Empty string key is not skipped') -490,493c394,395 -< it('data passed to helpers', function() { -< expectTemplate( -< '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}' -< ) ---- -> it('data passed to helpers', () => { -> expectTemplate('{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}') -495c397,399 -< .withMessage('should output data') ---- -> .withHelper('detectDataInsideEach', function (options) { -> return options.data && options.data.exclaim; -> }) -498,499c402,403 -< exclaim: '!' -< } ---- -> exclaim: '!', -> }, -504,508c408,409 -< it('each on implicit context', function() { -< expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow( -< handlebarsEnv.Exception, -< 'Must pass iterator to #each' -< ); ---- -> it('each on implicit context', () => { -> expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(Handlebars.Exception); -511,513c412,417 -< if (global.Symbol && global.Symbol.iterator) { -< it('each on iterable', function() { -< function Iterator(arr) { ---- -> it('each on iterable', () => { -> class Iterator { -> private arr: any[]; -> private index: number = 0; -> -> constructor(arr: any[]) { -515d418 -< this.index = 0; -517,519c420,423 -< Iterator.prototype.next = function() { -< var value = this.arr[this.index]; -< var done = this.index === this.arr.length; ---- -> -> next() { -> const value = this.arr[this.index]; -> const done = this.index === this.arr.length; -523,525c427,434 -< return { value: value, done: done }; -< }; -< function Iterable(arr) { ---- -> return { value, done }; -> } -> } -> -> class Iterable { -> private arr: any[]; -> -> constructor(arr: any[]) { -528c437,438 -< Iterable.prototype[global.Symbol.iterator] = function() { ---- -> -> [Symbol.iterator]() { -530,531c440,441 -< }; -< var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; ---- -> } -> } -533,545c443 -< expectTemplate(string) -< .withInput({ -< goodbyes: new Iterable([ -< { text: 'goodbye' }, -< { text: 'Goodbye' }, -< { text: 'GOODBYE' } -< ]), -< world: 'world' -< }) -< .withMessage( -< 'each with array argument iterates over the contents when not empty' -< ) -< .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); ---- -> const string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!'; -547,557c445,458 -< expectTemplate(string) -< .withInput({ -< goodbyes: new Iterable([]), -< world: 'world' -< }) -< .withMessage( -< 'each with array argument ignores the contents when empty' -< ) -< .toCompileTo('cruel world!'); -< }); -< } ---- -> expectTemplate(string) -> .withInput({ -> goodbyes: new Iterable([{ text: 'goodbye' }, { text: 'Goodbye' }, { text: 'GOODBYE' }]), -> world: 'world', -> }) -> .toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!'); -> -> expectTemplate(string) -> .withInput({ -> goodbyes: new Iterable([]), -> world: 'world', -> }) -> .toCompileTo('cruel world!'); -> }); -560c461 -< describe('#log', function() { ---- -> describe('#log', function () { -562,564c463,465 -< if (typeof console === 'undefined') { -< return; -< } ---- -> let $log: typeof console.log; -> let $info: typeof console.info; -> let $error: typeof console.error; -566,567c467 -< var $log, $info, $error; -< beforeEach(function() { ---- -> beforeEach(function () { -570a471,472 -> -> global.kbnHandlebarsEnv = Handlebars.create(); -572c474,475 -< afterEach(function() { ---- -> -> afterEach(function () { -575a479,480 -> -> global.kbnHandlebarsEnv = null; -578,580c483,486 -< it('should call logger at default level', function() { -< var levelArg, logArg; -< handlebarsEnv.log = function(level, arg) { ---- -> it('should call logger at default level', function () { -> let levelArg; -> let logArg; -> kbnHandlebarsEnv!.log = function (level, arg) { -585,590c491,493 -< expectTemplate('{{log blah}}') -< .withInput({ blah: 'whee' }) -< .withMessage('log should not display') -< .toCompileTo(''); -< equals(1, levelArg, 'should call log with 1'); -< equals('whee', logArg, "should call log with 'whee'"); ---- -> expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); -> expect(1).toEqual(levelArg); -> expect('whee').toEqual(logArg); -593,595c496,499 -< it('should call logger at data level', function() { -< var levelArg, logArg; -< handlebarsEnv.log = function(level, arg) { ---- -> it('should call logger at data level', function () { -> let levelArg; -> let logArg; -> kbnHandlebarsEnv!.log = function (level, arg) { -605,606c509,510 -< equals('03', levelArg); -< equals('whee', logArg); ---- -> expect('03').toEqual(levelArg); -> expect('whee').toEqual(logArg); -609,610c513,515 -< it('should output to info', function() { -< var called; ---- -> it('should output to info', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -612,616c517,523 -< console.info = function(info) { -< equals('whee', info); -< called = true; -< console.info = $info; -< console.log = $log; ---- -> console.info = function (info) { -> expect('whee').toEqual(info); -> calls++; -> if (calls === callsExpected) { -> console.info = $info; -> console.log = $log; -> } -618,622c525,531 -< console.log = function(log) { -< equals('whee', log); -< called = true; -< console.info = $info; -< console.log = $log; ---- -> console.log = function (log) { -> expect('whee').toEqual(log); -> calls++; -> if (calls === callsExpected) { -> console.info = $info; -> console.log = $log; -> } -625,628c534,535 -< expectTemplate('{{log blah}}') -< .withInput({ blah: 'whee' }) -< .toCompileTo(''); -< equals(true, called); ---- -> expectTemplate('{{log blah}}').withInput({ blah: 'whee' }).toCompileTo(''); -> expect(calls).toEqual(callsExpected); -631,632c538,540 -< it('should log at data level', function() { -< var called; ---- -> it('should log at data level', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -634,637c542,545 -< console.error = function(log) { -< equals('whee', log); -< called = true; -< console.error = $error; ---- -> console.error = function (log) { -> expect('whee').toEqual(log); -> calls++; -> if (calls === callsExpected) console.error = $error; -645c553 -< equals(true, called); ---- -> expect(calls).toEqual(callsExpected); -648,649c556,558 -< it('should handle missing logger', function() { -< var called = false; ---- -> it('should handle missing logger', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -650a560 -> // @ts-expect-error -652,655c562,565 -< console.log = function(log) { -< equals('whee', log); -< called = true; -< console.log = $log; ---- -> console.log = function (log) { -> expect('whee').toEqual(log); -> calls++; -> if (calls === callsExpected) console.log = $log; -663c573 -< equals(true, called); ---- -> expect(calls).toEqual(callsExpected); -666,667c576,578 -< it('should handle string log levels', function() { -< var called; ---- -> it('should handle string log levels', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -669,671c580,582 -< console.error = function(log) { -< equals('whee', log); -< called = true; ---- -> console.error = function (log) { -> expect('whee').toEqual(log); -> calls++; -679c590 -< equals(true, called); ---- -> expect(calls).toEqual(callsExpected); -681c592 -< called = false; ---- -> calls = 0; -688c599 -< equals(true, called); ---- -> expect(calls).toEqual(callsExpected); -691,692c602,604 -< it('should handle hash log levels', function() { -< var called; ---- -> it('should handle hash log levels [1]', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -694,696c606,608 -< console.error = function(log) { -< equals('whee', log); -< called = true; ---- -> console.error = function (log) { -> expect('whee').toEqual(log); -> calls++; -699,702c611,612 -< expectTemplate('{{log blah level="error"}}') -< .withInput({ blah: 'whee' }) -< .toCompileTo(''); -< equals(true, called); ---- -> expectTemplate('{{log blah level="error"}}').withInput({ blah: 'whee' }).toCompileTo(''); -> expect(calls).toEqual(callsExpected); -705,706c615,616 -< it('should handle hash log levels', function() { -< var called = false; ---- -> it('should handle hash log levels [2]', function () { -> let called = false; -708,711c618,625 -< console.info = console.log = console.error = console.debug = function() { -< called = true; -< console.info = console.log = console.error = console.debug = $log; -< }; ---- -> console.info = -> console.log = -> console.error = -> console.debug = -> function () { -> called = true; -> console.info = console.log = console.error = console.debug = $log; -> }; -713,716c627,628 -< expectTemplate('{{log blah level="debug"}}') -< .withInput({ blah: 'whee' }) -< .toCompileTo(''); -< equals(false, called); ---- -> expectTemplate('{{log blah level="debug"}}').withInput({ blah: 'whee' }).toCompileTo(''); -> expect(false).toEqual(called); -719,720c631,633 -< it('should pass multiple log arguments', function() { -< var called; ---- -> it('should pass multiple log arguments', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -722,727c635,640 -< console.info = console.log = function(log1, log2, log3) { -< equals('whee', log1); -< equals('foo', log2); -< equals(1, log3); -< called = true; -< console.log = $log; ---- -> console.info = console.log = function (log1, log2, log3) { -> expect('whee').toEqual(log1); -> expect('foo').toEqual(log2); -> expect(1).toEqual(log3); -> calls++; -> if (calls === callsExpected) console.log = $log; -730,733c643,644 -< expectTemplate('{{log blah "foo" 1}}') -< .withInput({ blah: 'whee' }) -< .toCompileTo(''); -< equals(true, called); ---- -> expectTemplate('{{log blah "foo" 1}}').withInput({ blah: 'whee' }).toCompileTo(''); -> expect(calls).toEqual(callsExpected); -736,737c647,649 -< it('should pass zero log arguments', function() { -< var called; ---- -> it('should pass zero log arguments', function () { -> let calls = 0; -> const callsExpected = process.env.AST || process.env.EVAL ? 1 : 2; -739,742c651,654 -< console.info = console.log = function() { -< expect(arguments.length).to.equal(0); -< called = true; -< console.log = $log; ---- -> console.info = console.log = function () { -> expect(arguments.length).toEqual(0); -> calls++; -> if (calls === callsExpected) console.log = $log; -745,748c657,658 -< expectTemplate('{{log}}') -< .withInput({ blah: 'whee' }) -< .toCompileTo(''); -< expect(called).to.be.true(); ---- -> expectTemplate('{{log}}').withInput({ blah: 'whee' }).toCompileTo(''); -> expect(calls).toEqual(callsExpected); -753,754c663,664 -< describe('#lookup', function() { -< it('should lookup arbitrary content', function() { ---- -> describe('#lookup', () => { -> it('should lookup arbitrary content', () => { -760c670 -< it('should not fail on undefined value', function() { ---- -> it('should not fail on undefined value', () => { diff --git a/packages/kbn-handlebars/.patches/compiler.patch b/packages/kbn-handlebars/.patches/compiler.patch deleted file mode 100644 index 571519b259c3b..0000000000000 --- a/packages/kbn-handlebars/.patches/compiler.patch +++ /dev/null @@ -1,269 +0,0 @@ -1,10c1,6 -< describe('compiler', function() { -< if (!Handlebars.compile) { -< return; -< } -< -< describe('#equals', function() { -< function compile(string) { -< var ast = Handlebars.parse(string); -< return new Handlebars.Compiler().compile(ast, {}); -< } ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -12,60c8,9 -< it('should treat as equal', function() { -< equal(compile('foo').equals(compile('foo')), true); -< equal(compile('{{foo}}').equals(compile('{{foo}}')), true); -< equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true); -< equal( -< compile('{{foo.bar baz "foo" true false bat=1}}').equals( -< compile('{{foo.bar baz "foo" true false bat=1}}') -< ), -< true -< ); -< equal( -< compile('{{foo.bar (baz bat=1)}}').equals( -< compile('{{foo.bar (baz bat=1)}}') -< ), -< true -< ); -< equal( -< compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), -< true -< ); -< }); -< it('should treat as not equal', function() { -< equal(compile('foo').equals(compile('bar')), false); -< equal(compile('{{foo}}').equals(compile('{{bar}}')), false); -< equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false); -< equal( -< compile('{{foo.bar baz bat=1}}').equals( -< compile('{{foo.bar bar bat=1}}') -< ), -< false -< ); -< equal( -< compile('{{foo.bar (baz bat=1)}}').equals( -< compile('{{foo.bar (bar bat=1)}}') -< ), -< false -< ); -< equal( -< compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), -< false -< ); -< equal( -< compile('{{#foo}} {{/foo}}').equals( -< compile('{{#foo}} {{foo}}{{/foo}}') -< ), -< false -< ); -< }); -< }); ---- -> import Handlebars from '../..'; -> import { forEachCompileFunctionName } from '../__jest__/test_bench'; -62,78c11,13 -< describe('#compile', function() { -< it('should fail with invalid input', function() { -< shouldThrow( -< function() { -< Handlebars.compile(null); -< }, -< Error, -< 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null' -< ); -< shouldThrow( -< function() { -< Handlebars.compile({}); -< }, -< Error, -< 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]' -< ); -< }); ---- -> describe('compiler', () => { -> forEachCompileFunctionName((compileName) => { -> const compile = Handlebars[compileName].bind(Handlebars); -80,92c15,20 -< it('should include the location in the error (row and column)', function() { -< try { -< Handlebars.compile(' \n {{#if}}\n{{/def}}')(); -< equal( -< true, -< false, -< 'Statement must throw exception. This line should not be executed.' -< ); -< } catch (err) { -< equal( -< err.message, -< "if doesn't match def - 2:5", -< 'Checking error message' ---- -> describe(`#${compileName}`, () => { -> it('should fail with invalid input', () => { -> expect(function () { -> compile(null); -> }).toThrow( -> `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed null` -94,102d21 -< if (Object.getOwnPropertyDescriptor(err, 'column').writable) { -< // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, -< // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) -< // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. -< equal(err.column, 5, 'Checking error column'); -< } -< equal(err.lineNumber, 2, 'Checking error row'); -< } -< }); -104,116c23,26 -< it('should include the location as enumerable property', function() { -< try { -< Handlebars.compile(' \n {{#if}}\n{{/def}}')(); -< equal( -< true, -< false, -< 'Statement must throw exception. This line should not be executed.' -< ); -< } catch (err) { -< equal( -< Object.prototype.propertyIsEnumerable.call(err, 'column'), -< true, -< 'Checking error column' ---- -> expect(function () { -> compile({}); -> }).toThrow( -> `You must pass a string or Handlebars AST to Handlebars.${compileName}. You passed [object Object]` -118,129c28 -< } -< }); -< -< it('can utilize AST instance', function() { -< equal( -< Handlebars.compile({ -< type: 'Program', -< body: [{ type: 'ContentStatement', value: 'Hello' }] -< })(), -< 'Hello' -< ); -< }); ---- -> }); -131,133c30,44 -< it('can pass through an empty string', function() { -< equal(Handlebars.compile('')(), ''); -< }); ---- -> it('should include the location in the error (row and column)', () => { -> try { -> compile(' \n {{#if}}\n{{/def}}')(); -> expect(true).toEqual(false); -> } catch (err) { -> expect(err.message).toEqual("if doesn't match def - 2:5"); -> if (Object.getOwnPropertyDescriptor(err, 'column')!.writable) { -> // In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty, -> // its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482) -> // Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers. -> expect(err.column).toEqual(5); -> } -> expect(err.lineNumber).toEqual(2); -> } -> }); -135,142c46,53 -< it('should not modify the options.data property(GH-1327)', function() { -< var options = { data: [{ a: 'foo' }, { a: 'bar' }] }; -< Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); -< equal( -< JSON.stringify(options, 0, 2), -< JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2) -< ); -< }); ---- -> it('should include the location as enumerable property', () => { -> try { -> compile(' \n {{#if}}\n{{/def}}')(); -> expect(true).toEqual(false); -> } catch (err) { -> expect(Object.prototype.propertyIsEnumerable.call(err, 'column')).toEqual(true); -> } -> }); -144,152c55,62 -< it('should not modify the options.knownHelpers property(GH-1327)', function() { -< var options = { knownHelpers: {} }; -< Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); -< equal( -< JSON.stringify(options, 0, 2), -< JSON.stringify({ knownHelpers: {} }, 0, 2) -< ); -< }); -< }); ---- -> it('can utilize AST instance', () => { -> expect( -> compile({ -> type: 'Program', -> body: [{ type: 'ContentStatement', value: 'Hello' }], -> })() -> ).toEqual('Hello'); -> }); -154,170c64,66 -< describe('#precompile', function() { -< it('should fail with invalid input', function() { -< shouldThrow( -< function() { -< Handlebars.precompile(null); -< }, -< Error, -< 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null' -< ); -< shouldThrow( -< function() { -< Handlebars.precompile({}); -< }, -< Error, -< 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]' -< ); -< }); ---- -> it('can pass through an empty string', () => { -> expect(compile('')()).toEqual(''); -> }); -172,182c68,75 -< it('can utilize AST instance', function() { -< equal( -< /return "Hello"/.test( -< Handlebars.precompile({ -< type: 'Program', -< body: [{ type: 'ContentStatement', value: 'Hello' }] -< }) -< ), -< true -< ); -< }); ---- -> it('should not modify the options.data property(GH-1327)', () => { -> // The `data` property is supposed to be a boolean, but in this test we want to ignore that -> const options = { data: [{ a: 'foo' }, { a: 'bar' }] as unknown as boolean }; -> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); -> expect(JSON.stringify(options, null, 2)).toEqual( -> JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, null, 2) -> ); -> }); -184,185c77,83 -< it('can pass through an empty string', function() { -< equal(/return ""/.test(Handlebars.precompile('')), true); ---- -> it('should not modify the options.knownHelpers property(GH-1327)', () => { -> const options = { knownHelpers: {} }; -> compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)(); -> expect(JSON.stringify(options, null, 2)).toEqual( -> JSON.stringify({ knownHelpers: {} }, null, 2) -> ); -> }); diff --git a/packages/kbn-handlebars/.patches/data.patch b/packages/kbn-handlebars/.patches/data.patch deleted file mode 100644 index 037214dddc3a1..0000000000000 --- a/packages/kbn-handlebars/.patches/data.patch +++ /dev/null @@ -1,276 +0,0 @@ -1,2c1,12 -< describe('data', function() { -< it('passing in data to a compiled function that expects data - works with helpers', function() { ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('data', () => { -> it('passing in data to a compiled function that expects data - works with helpers', () => { -5c15 -< .withHelper('hello', function(options) { ---- -> .withHelper('hello', function (this: any, options) { -10d19 -< .withMessage('Data output by helper') -14c23 -< it('data can be looked up via @foo', function() { ---- -> it('data can be looked up via @foo', () => { -17d25 -< .withMessage('@foo retrieves template data') -21,22c29,31 -< it('deep @foo triggers automatic top-level data', function() { -< var helpers = Handlebars.createFrame(handlebarsEnv.helpers); ---- -> it('deep @foo triggers automatic top-level data', () => { -> global.kbnHandlebarsEnv = Handlebars.create(); -> const helpers = Handlebars.createFrame(kbnHandlebarsEnv!.helpers); -24,25c33,34 -< helpers.let = function(options) { -< var frame = Handlebars.createFrame(options.data); ---- -> helpers.let = function (options: Handlebars.HelperOptions) { -> const frame = Handlebars.createFrame(options.data); -27c36 -< for (var prop in options.hash) { ---- -> for (const prop in options.hash) { -40d48 -< .withMessage('Automatic data was triggered') -41a50,51 -> -> global.kbnHandlebarsEnv = null; -44c54 -< it('parameter data can be looked up via @foo', function() { ---- -> it('parameter data can be looked up via @foo', () => { -47c57 -< .withHelper('hello', function(noun) { ---- -> .withHelper('hello', function (noun) { -50d59 -< .withMessage('@foo as a parameter retrieves template data') -54c63 -< it('hash values can be looked up via @foo', function() { ---- -> it('hash values can be looked up via @foo', () => { -57c66 -< .withHelper('hello', function(options) { ---- -> .withHelper('hello', function (options) { -60d68 -< .withMessage('@foo as a parameter retrieves template data') -64c72 -< it('nested parameter data can be looked up via @foo.bar', function() { ---- -> it('nested parameter data can be looked up via @foo.bar', () => { -67c75 -< .withHelper('hello', function(noun) { ---- -> .withHelper('hello', function (noun) { -70d77 -< .withMessage('@foo as a parameter retrieves template data') -74c81 -< it('nested parameter data does not fail with @world.bar', function() { ---- -> it('nested parameter data does not fail with @world.bar', () => { -77c84 -< .withHelper('hello', function(noun) { ---- -> .withHelper('hello', function (noun) { -80d86 -< .withMessage('@foo as a parameter retrieves template data') -84,87c90,91 -< it('parameter data throws when using complex scope references', function() { -< expectTemplate( -< '{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}' -< ).toThrow(Error); ---- -> it('parameter data throws when using complex scope references', () => { -> expectTemplate('{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}').toThrow(Error); -90c94 -< it('data can be functions', function() { ---- -> it('data can be functions', () => { -94c98 -< hello: function() { ---- -> hello() { -96,97c100,101 -< } -< } ---- -> }, -> }, -102c106 -< it('data can be functions with params', function() { ---- -> it('data can be functions with params', () => { -106c110 -< hello: function(arg) { ---- -> hello(arg: any) { -108,109c112,113 -< } -< } ---- -> }, -> }, -114c118 -< it('data is inherited downstream', function() { ---- -> it('data is inherited downstream', () => { -120,122c124,126 -< .withHelper('let', function(options) { -< var frame = Handlebars.createFrame(options.data); -< for (var prop in options.hash) { ---- -> .withHelper('let', function (this: any, options) { -> const frame = Handlebars.createFrame(options.data); -> for (const prop in options.hash) { -130d133 -< .withMessage('data variables are inherited downstream') -134,147c137 -< it('passing in data to a compiled function that expects data - works with helpers in partials', function() { -< expectTemplate('{{>myPartial}}') -< .withCompileOptions({ data: true }) -< .withPartial('myPartial', '{{hello}}') -< .withHelper('hello', function(options) { -< return options.data.adjective + ' ' + this.noun; -< }) -< .withInput({ noun: 'cat' }) -< .withRuntimeOptions({ data: { adjective: 'happy' } }) -< .withMessage('Data output by helper inside partial') -< .toCompileTo('happy cat'); -< }); -< -< it('passing in data to a compiled function that expects data - works with helpers and parameters', function() { ---- -> it('passing in data to a compiled function that expects data - works with helpers and parameters', () => { -150c140 -< .withHelper('hello', function(noun, options) { ---- -> .withHelper('hello', function (this: any, noun, options) { -155d144 -< .withMessage('Data output by helper') -159c148 -< it('passing in data to a compiled function that expects data - works with block helpers', function() { ---- -> it('passing in data to a compiled function that expects data - works with block helpers', () => { -162c151 -< data: true ---- -> data: true, -164c153 -< .withHelper('hello', function(options) { ---- -> .withHelper('hello', function (this: any, options) { -167c156 -< .withHelper('world', function(options) { ---- -> .withHelper('world', function (this: any, options) { -172d160 -< .withMessage('Data output by helper') -176c164 -< it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() { ---- -> it('passing in data to a compiled function that expects data - works with block helpers that use ..', () => { -179c167 -< .withHelper('hello', function(options) { ---- -> .withHelper('hello', function (options) { -182c170 -< .withHelper('world', function(thing, options) { ---- -> .withHelper('world', function (this: any, thing, options) { -187d174 -< .withMessage('Data output by helper') -191c178 -< it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() { ---- -> it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', () => { -194c181 -< .withHelper('hello', function(options) { ---- -> .withHelper('hello', function (options) { -197c184 -< .withHelper('world', function(thing, options) { ---- -> .withHelper('world', function (this: any, thing, options) { -202d188 -< .withMessage('Data output by helper') -206c192 -< it('you can override inherited data when invoking a helper', function() { ---- -> it('you can override inherited data when invoking a helper', () => { -209,213c195,196 -< .withHelper('hello', function(options) { -< return options.fn( -< { exclaim: '?', zomg: 'world' }, -< { data: { adjective: 'sad' } } -< ); ---- -> .withHelper('hello', function (options) { -> return options.fn({ exclaim: '?', zomg: 'world' }, { data: { adjective: 'sad' } }); -215c198 -< .withHelper('world', function(thing, options) { ---- -> .withHelper('world', function (this: any, thing, options) { -220d202 -< .withMessage('Overriden data output by helper') -224c206 -< it('you can override inherited data when invoking a helper with depth', function() { ---- -> it('you can override inherited data when invoking a helper with depth', () => { -227c209 -< .withHelper('hello', function(options) { ---- -> .withHelper('hello', function (options) { -230c212 -< .withHelper('world', function(thing, options) { ---- -> .withHelper('world', function (this: any, thing, options) { -235d216 -< .withMessage('Overriden data output by helper') -239,240c220,221 -< describe('@root', function() { -< it('the root context can be looked up via @root', function() { ---- -> describe('@root', () => { -> it('the root context can be looked up via @root', () => { -246,248c227 -< expectTemplate('{{@root.foo}}') -< .withInput({ foo: 'hello' }) -< .toCompileTo('hello'); ---- -> expectTemplate('{{@root.foo}}').withInput({ foo: 'hello' }).toCompileTo('hello'); -251c230 -< it('passed root values take priority', function() { ---- -> it('passed root values take priority', () => { -259,260c238,239 -< describe('nesting', function() { -< it('the root context can be looked up via @root', function() { ---- -> describe('nesting', () => { -> it('the root context can be looked up via @root', () => { -265,266c244,245 -< .withHelper('helper', function(options) { -< var frame = Handlebars.createFrame(options.data); ---- -> .withHelper('helper', function (this: any, options) { -> const frame = Handlebars.createFrame(options.data); -272,273c251,252 -< depth: 0 -< } ---- -> depth: 0, -> }, diff --git a/packages/kbn-handlebars/.patches/helpers.patch b/packages/kbn-handlebars/.patches/helpers.patch deleted file mode 100644 index 09774103fb587..0000000000000 --- a/packages/kbn-handlebars/.patches/helpers.patch +++ /dev/null @@ -1,1108 +0,0 @@ -1,2c1,20 -< describe('helpers', function() { -< it('helper with complex lookup$', function() { ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> beforeEach(() => { -> global.kbnHandlebarsEnv = Handlebars.create(); -> }); -> -> afterEach(() => { -> global.kbnHandlebarsEnv = null; -> }); -> -> describe('helpers', () => { -> it('helper with complex lookup$', () => { -6c24 -< goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] ---- -> goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], -8,11c26,27 -< .withHelper('link', function(prefix) { -< return ( -< '' + this.text + '' -< ); ---- -> .withHelper('link', function (this: any, prefix) { -> return '' + this.text + ''; -16c32 -< it('helper for raw block gets raw content', function() { ---- -> it('helper for raw block gets raw content', () => { -19c35 -< .withHelper('raw', function(options) { ---- -> .withHelper('raw', function (options: Handlebars.HelperOptions) { -22d37 -< .withMessage('raw block helper gets raw content') -26c41 -< it('helper for raw block gets parameters', function() { ---- -> it('helper for raw block gets parameters', () => { -29,30c44,46 -< .withHelper('raw', function(a, b, c, options) { -< return options.fn() + a + b + c; ---- -> .withHelper('raw', function (a, b, c, options: Handlebars.HelperOptions) { -> const ret = options.fn() + a + b + c; -> return ret; -32d47 -< .withMessage('raw block helper gets raw content') -36,37c51,52 -< describe('raw block parsing (with identity helper-function)', function() { -< function runWithIdentityHelper(template, expected) { ---- -> describe('raw block parsing (with identity helper-function)', () => { -> function runWithIdentityHelper(template: string, expected: string) { -39c54 -< .withHelper('identity', function(options) { ---- -> .withHelper('identity', function (options: Handlebars.HelperOptions) { -45c60 -< it('helper for nested raw block gets raw content', function() { ---- -> it('helper for nested raw block gets raw content', () => { -52c67 -< it('helper for nested raw block works with empty content', function() { ---- -> it('helper for nested raw block works with empty content', () => { -56c71 -< xit('helper for nested raw block works if nested raw blocks are broken', function() { ---- -> it.skip('helper for nested raw block works if nested raw blocks are broken', () => { -67c82 -< it('helper for nested raw block closes after first matching close', function() { ---- -> it('helper for nested raw block closes after first matching close', () => { -74,75c89,90 -< it('helper for nested raw block throw exception when with missing closing braces', function() { -< var string = '{{{{a}}}} {{{{/a'; ---- -> it('helper for nested raw block throw exception when with missing closing braces', () => { -> const string = '{{{{a}}}} {{{{/a'; -80c95 -< it('helper block with identical context', function() { ---- -> it('helper block with identical context', () => { -83,86c98,101 -< .withHelper('goodbyes', function(options) { -< var out = ''; -< var byes = ['Goodbye', 'goodbye', 'GOODBYE']; -< for (var i = 0, j = byes.length; i < j; i++) { ---- -> .withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) { -> let out = ''; -> const byes = ['Goodbye', 'goodbye', 'GOODBYE']; -> for (let i = 0, j = byes.length; i < j; i++) { -94c109 -< it('helper block with complex lookup expression', function() { ---- -> it('helper block with complex lookup expression', () => { -97,100c112,115 -< .withHelper('goodbyes', function(options) { -< var out = ''; -< var byes = ['Goodbye', 'goodbye', 'GOODBYE']; -< for (var i = 0, j = byes.length; i < j; i++) { ---- -> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) { -> let out = ''; -> const byes = ['Goodbye', 'goodbye', 'GOODBYE']; -> for (let i = 0, j = byes.length; i < j; i++) { -108,111c123,124 -< it('helper with complex lookup and nested template', function() { -< expectTemplate( -< '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' -< ) ---- -> it('helper with complex lookup and nested template', () => { -> expectTemplate('{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}') -114c127 -< goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] ---- -> goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], -116,125c129,130 -< .withHelper('link', function(prefix, options) { -< return ( -< '' + -< options.fn(this) + -< '' -< ); ---- -> .withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) { -> return '' + options.fn(this) + ''; -130,133c135,136 -< it('helper with complex lookup and nested template in VM+Compiler', function() { -< expectTemplate( -< '{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}' -< ) ---- -> it('helper with complex lookup and nested template in VM+Compiler', () => { -> expectTemplate('{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}') -136c139 -< goodbyes: [{ text: 'Goodbye', url: 'goodbye' }] ---- -> goodbyes: [{ text: 'Goodbye', url: 'goodbye' }], -138,147c141,142 -< .withHelper('link', function(prefix, options) { -< return ( -< '' + -< options.fn(this) + -< '' -< ); ---- -> .withHelper('link', function (this: any, prefix, options: Handlebars.HelperOptions) { -> return '' + options.fn(this) + ''; -152c147 -< it('helper returning undefined value', function() { ---- -> it('helper returning undefined value', () => { -155c150 -< nothere: function() {} ---- -> nothere() {}, -161c156 -< nothere: function() {} ---- -> nothere() {}, -166c161 -< it('block helper', function() { ---- -> it('block helper', () => { -169c164 -< .withHelper('goodbyes', function(options) { ---- -> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) { -172d166 -< .withMessage('Block helper executed') -176c170 -< it('block helper staying in the same context', function() { ---- -> it('block helper staying in the same context', () => { -179c173 -< .withHelper('form', function(options) { ---- -> .withHelper('form', function (this: any, options: Handlebars.HelperOptions) { -182d175 -< .withMessage('Block helper executed with current context') -186,187c179,180 -< it('block helper should have context in this', function() { -< function link(options) { ---- -> it('block helper should have context in this', () => { -> function link(this: any, options: Handlebars.HelperOptions) { -191,193c184 -< expectTemplate( -< '
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
' -< ) ---- -> expectTemplate('
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
') -197,198c188,189 -< { name: 'Yehuda', id: 2 } -< ] ---- -> { name: 'Yehuda', id: 2 }, -> ], -206c197 -< it('block helper for undefined value', function() { ---- -> it('block helper for undefined value', () => { -210c201 -< it('block helper passing a new context', function() { ---- -> it('block helper passing a new context', () => { -213c204 -< .withHelper('form', function(context, options) { ---- -> .withHelper('form', function (context, options: Handlebars.HelperOptions) { -216d206 -< .withMessage('Context variable resolved') -220c210 -< it('block helper passing a complex path context', function() { ---- -> it('block helper passing a complex path context', () => { -223c213 -< .withHelper('form', function(context, options) { ---- -> .withHelper('form', function (context, options: Handlebars.HelperOptions) { -226d215 -< .withMessage('Complex path variable resolved') -230,233c219,220 -< it('nested block helpers', function() { -< expectTemplate( -< '{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}' -< ) ---- -> it('nested block helpers', () => { -> expectTemplate('{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}') -235c222 -< yehuda: { name: 'Yehuda' } ---- -> yehuda: { name: 'Yehuda' }, -237c224 -< .withHelper('link', function(options) { ---- -> .withHelper('link', function (this: any, options: Handlebars.HelperOptions) { -240c227 -< .withHelper('form', function(context, options) { ---- -> .withHelper('form', function (context, options: Handlebars.HelperOptions) { -243d229 -< .withMessage('Both blocks executed') -247,249c233,235 -< it('block helper inverted sections', function() { -< var string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; -< function list(context, options) { ---- -> it('block helper inverted sections', () => { -> const string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}"; -> function list(this: any, context: any, options: Handlebars.HelperOptions) { -251,252c237,238 -< var out = '
    '; -< for (var i = 0, j = context.length; i < j; i++) { ---- -> let out = '
      '; -> for (let i = 0, j = context.length; i < j; i++) { -268,269c254 -< .withHelpers({ list: list }) -< .withMessage('an inverse wrapper is passed in as a new context') ---- -> .withHelpers({ list }) -274,275c259 -< .withHelpers({ list: list }) -< .withMessage('an inverse wrapper can be optionally called') ---- -> .withHelpers({ list }) -281c265 -< message: "Nobody's here" ---- -> message: "Nobody's here", -283,284c267 -< .withHelpers({ list: list }) -< .withMessage('the context of an inverse is the parent of the block') ---- -> .withHelpers({ list }) -288,292c271,273 -< it('pathed lambas with parameters', function() { -< var hash = { -< helper: function() { -< return 'winning'; -< } ---- -> it('pathed lambas with parameters', () => { -> const hash = { -> helper: () => 'winning', -293a275 -> // @ts-expect-error -295,299d276 -< var helpers = { -< './helper': function() { -< return 'fail'; -< } -< }; -301,304c278,280 -< expectTemplate('{{./helper 1}}') -< .withInput(hash) -< .withHelpers(helpers) -< .toCompileTo('winning'); ---- -> const helpers = { -> './helper': () => 'fail', -> }; -306,309c282,283 -< expectTemplate('{{hash/helper 1}}') -< .withInput(hash) -< .withHelpers(helpers) -< .toCompileTo('winning'); ---- -> expectTemplate('{{./helper 1}}').withInput(hash).withHelpers(helpers).toCompileTo('winning'); -> expectTemplate('{{hash/helper 1}}').withInput(hash).withHelpers(helpers).toCompileTo('winning'); -312,313c286,287 -< describe('helpers hash', function() { -< it('providing a helpers hash', function() { ---- -> describe('helpers hash', () => { -> it('providing a helpers hash', () => { -317c291 -< world: function() { ---- -> world() { -319c293 -< } ---- -> }, -321d294 -< .withMessage('helpers hash is available') -327c300 -< world: function() { ---- -> world() { -329c302 -< } ---- -> }, -331d303 -< .withMessage('helpers hash is available inside other blocks') -335c307 -< it('in cases of conflict, helpers win', function() { ---- -> it('in cases of conflict, helpers win', () => { -339c311 -< lookup: function() { ---- -> lookup() { -341c313 -< } ---- -> }, -343d314 -< .withMessage('helpers hash has precedence escaped expansion') -349c320 -< lookup: function() { ---- -> lookup() { -351c322 -< } ---- -> }, -353d323 -< .withMessage('helpers hash has precedence simple expansion') -357c327 -< it('the helpers hash is available is nested contexts', function() { ---- -> it('the helpers hash is available is nested contexts', () => { -361c331 -< helper: function() { ---- -> helper() { -363c333 -< } ---- -> }, -365d334 -< .withMessage('helpers hash is available in nested contexts.') -369,370c338,339 -< it('the helper hash should augment the global hash', function() { -< handlebarsEnv.registerHelper('test_helper', function() { ---- -> it('the helper hash should augment the global hash', () => { -> kbnHandlebarsEnv!.registerHelper('test_helper', function () { -374,376c343 -< expectTemplate( -< '{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' -< ) ---- -> expectTemplate('{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}') -379c346 -< world: function() { ---- -> world() { -381c348 -< } ---- -> }, -387,389c354,356 -< describe('registration', function() { -< it('unregisters', function() { -< handlebarsEnv.helpers = {}; ---- -> describe('registration', () => { -> it('unregisters', () => { -> deleteAllKeys(kbnHandlebarsEnv!.helpers); -391c358 -< handlebarsEnv.registerHelper('foo', function() { ---- -> kbnHandlebarsEnv!.registerHelper('foo', function () { -394,395c361,363 -< handlebarsEnv.unregisterHelper('foo'); -< equals(handlebarsEnv.helpers.foo, undefined); ---- -> expect(kbnHandlebarsEnv!.helpers.foo).toBeDefined(); -> kbnHandlebarsEnv!.unregisterHelper('foo'); -> expect(kbnHandlebarsEnv!.helpers.foo).toBeUndefined(); -398,400c366,368 -< it('allows multiple globals', function() { -< var helpers = handlebarsEnv.helpers; -< handlebarsEnv.helpers = {}; ---- -> it('allows multiple globals', () => { -> const ifHelper = kbnHandlebarsEnv!.helpers.if; -> deleteAllKeys(kbnHandlebarsEnv!.helpers); -402,404c370,372 -< handlebarsEnv.registerHelper({ -< if: helpers['if'], -< world: function() { ---- -> kbnHandlebarsEnv!.registerHelper({ -> if: ifHelper, -> world() { -407c375 -< testHelper: function() { ---- -> testHelper() { -409c377 -< } ---- -> }, -412,414c380 -< expectTemplate( -< '{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}' -< ) ---- -> expectTemplate('{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}') -419,429c385,391 -< it('fails with multiple and args', function() { -< shouldThrow( -< function() { -< handlebarsEnv.registerHelper( -< { -< world: function() { -< return 'world!'; -< }, -< testHelper: function() { -< return 'found it!'; -< } ---- -> it('fails with multiple and args', () => { -> expect(() => { -> kbnHandlebarsEnv!.registerHelper( -> // @ts-expect-error TypeScript is complaining about the invalid input just as the thrown error -> { -> world() { -> return 'world!'; -431,436c393,399 -< {} -< ); -< }, -< Error, -< 'Arg not supported with multiple helpers' -< ); ---- -> testHelper() { -> return 'found it!'; -> }, -> }, -> {} -> ); -> }).toThrow('Arg not supported with multiple helpers'); -440c403 -< it('decimal number literals work', function() { ---- -> it('decimal number literals work', () => { -442c405 -< .withHelper('hello', function(times, times2) { ---- -> .withHelper('hello', function (times, times2) { -451d413 -< .withMessage('template with a negative integer literal') -455c417 -< it('negative number literals work', function() { ---- -> it('negative number literals work', () => { -457c419 -< .withHelper('hello', function(times) { ---- -> .withHelper('hello', function (times) { -463d424 -< .withMessage('template with a negative integer literal') -467,468c428,429 -< describe('String literal parameters', function() { -< it('simple literals work', function() { ---- -> describe('String literal parameters', () => { -> it('simple literals work', () => { -470c431 -< .withHelper('hello', function(param, times, bool1, bool2) { ---- -> .withHelper('hello', function (param, times, bool1, bool2) { -480,482c441 -< return ( -< 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2 -< ); ---- -> return 'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2; -484d442 -< .withMessage('template with a simple String literal') -488c446 -< it('using a quote in the middle of a parameter raises an error', function() { ---- -> it('using a quote in the middle of a parameter raises an error', () => { -492c450 -< it('escaping a String is possible', function() { ---- -> it('escaping a String is possible', () => { -494c452 -< .withHelper('hello', function(param) { ---- -> .withHelper('hello', function (param) { -497d454 -< .withMessage('template with an escaped String literal') -501c458 -< it("it works with ' marks", function() { ---- -> it("it works with ' marks", () => { -503c460 -< .withHelper('hello', function(param) { ---- -> .withHelper('hello', function (param) { -506d462 -< .withMessage("template with a ' mark") -511,524c467,468 -< it('negative number literals work', function() { -< expectTemplate('Message: {{hello -12}}') -< .withHelper('hello', function(times) { -< if (typeof times !== 'number') { -< times = 'NaN'; -< } -< return 'Hello ' + times + ' times'; -< }) -< .withMessage('template with a negative integer literal') -< .toCompileTo('Message: Hello -12 times'); -< }); -< -< describe('multiple parameters', function() { -< it('simple multi-params work', function() { ---- -> describe('multiple parameters', () => { -> it('simple multi-params work', () => { -527c471 -< .withHelper('goodbye', function(cruel, world) { ---- -> .withHelper('goodbye', function (cruel, world) { -530d473 -< .withMessage('regular helpers with multiple params') -534,537c477,478 -< it('block multi-params work', function() { -< expectTemplate( -< 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}' -< ) ---- -> it('block multi-params work', () => { -> expectTemplate('Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}') -539c480 -< .withHelper('goodbye', function(cruel, world, options) { ---- -> .withHelper('goodbye', function (cruel, world, options: Handlebars.HelperOptions) { -542d482 -< .withMessage('block helpers with multiple params') -547,548c487,488 -< describe('hash', function() { -< it('helpers can take an optional hash', function() { ---- -> describe('hash', () => { -> it('helpers can take an optional hash', () => { -550c490 -< .withHelper('goodbye', function(options) { ---- -> .withHelper('goodbye', function (options: Handlebars.HelperOptions) { -561d500 -< .withMessage('Helper output hash') -565,566c504,505 -< it('helpers can take an optional hash with booleans', function() { -< function goodbye(options) { ---- -> it('helpers can take an optional hash with booleans', () => { -> function goodbye(options: Handlebars.HelperOptions) { -578d516 -< .withMessage('Helper output hash') -583d520 -< .withMessage('Boolean helper parameter honored') -587c524 -< it('block helpers can take an optional hash', function() { ---- -> it('block helpers can take an optional hash', () => { -589c526 -< .withHelper('goodbye', function(options) { ---- -> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) { -600d536 -< .withMessage('Hash parameters output') -604c540 -< it('block helpers can take an optional hash with single quoted stings', function() { ---- -> it('block helpers can take an optional hash with single quoted stings', () => { -606c542 -< .withHelper('goodbye', function(options) { ---- -> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) { -617d552 -< .withMessage('Hash parameters output') -621,622c556,557 -< it('block helpers can take an optional hash with booleans', function() { -< function goodbye(options) { ---- -> it('block helpers can take an optional hash with booleans', () => { -> function goodbye(this: any, options: Handlebars.HelperOptions) { -634d568 -< .withMessage('Boolean hash parameter honored') -639d572 -< .withMessage('Boolean hash parameter honored') -644,648c577,579 -< describe('helperMissing', function() { -< it('if a context is not found, helperMissing is used', function() { -< expectTemplate('{{hello}} {{link_to world}}').toThrow( -< /Missing helper: "link_to"/ -< ); ---- -> describe('helperMissing', () => { -> it('if a context is not found, helperMissing is used', () => { -> expectTemplate('{{hello}} {{link_to world}}').toThrow(/Missing helper: "link_to"/); -651c582 -< it('if a context is not found, custom helperMissing is used', function() { ---- -> it('if a context is not found, custom helperMissing is used', () => { -654c585 -< .withHelper('helperMissing', function(mesg, options) { ---- -> .withHelper('helperMissing', function (mesg, options: Handlebars.HelperOptions) { -662c593 -< it('if a value is not found, custom helperMissing is used', function() { ---- -> it('if a value is not found, custom helperMissing is used', () => { -665c596 -< .withHelper('helperMissing', function(options) { ---- -> .withHelper('helperMissing', function (options: Handlebars.HelperOptions) { -674,675c605,606 -< describe('knownHelpers', function() { -< it('Known helper should render helper', function() { ---- -> describe('knownHelpers', () => { -> it('Known helper should render helper', () => { -678c609 -< knownHelpers: { hello: true } ---- -> knownHelpers: { hello: true }, -680c611 -< .withHelper('hello', function() { ---- -> .withHelper('hello', function () { -686c617 -< it('Unknown helper in knownHelpers only mode should be passed as undefined', function() { ---- -> it('Unknown helper in knownHelpers only mode should be passed as undefined', () => { -690c621 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -692c623 -< .withHelper('typeof', function(arg) { ---- -> .withHelper('typeof', function (arg) { -695c626 -< .withHelper('hello', function() { ---- -> .withHelper('hello', function () { -701c632 -< it('Builtin helpers available in knownHelpers only mode', function() { ---- -> it('Builtin helpers available in knownHelpers only mode', () => { -704c635 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -709c640 -< it('Field lookup works in knownHelpers only mode', function() { ---- -> it('Field lookup works in knownHelpers only mode', () => { -712c643 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -718c649 -< it('Conditional blocks work in knownHelpers only mode', function() { ---- -> it('Conditional blocks work in knownHelpers only mode', () => { -721c652 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -727c658 -< it('Invert blocks work in knownHelpers only mode', function() { ---- -> it('Invert blocks work in knownHelpers only mode', () => { -730c661 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -736c667 -< it('Functions are bound to the context in knownHelpers only mode', function() { ---- -> it('Functions are bound to the context in knownHelpers only mode', () => { -739c670 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -742c673 -< foo: function() { ---- -> foo() { -745c676 -< bar: 'bar' ---- -> bar: 'bar', -750c681 -< it('Unknown helper call in knownHelpers only mode should throw', function() { ---- -> it('Unknown helper call in knownHelpers only mode should throw', () => { -757,758c688,689 -< describe('blockHelperMissing', function() { -< it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() { ---- -> describe('blockHelperMissing', () => { -> it('lambdas are resolved by blockHelperMissing, not handlebars proper', () => { -761c692 -< truthy: function() { ---- -> truthy() { -763c694 -< } ---- -> }, -768c699 -< it('lambdas resolved by blockHelperMissing are bound to the context', function() { ---- -> it('lambdas resolved by blockHelperMissing are bound to the context', () => { -771c702 -< truthy: function() { ---- -> truthy() { -774c705 -< truthiness: function() { ---- -> truthiness() { -776c707 -< } ---- -> }, -782,785c713,716 -< describe('name field', function() { -< var helpers = { -< blockHelperMissing: function() { -< return 'missing: ' + arguments[arguments.length - 1].name; ---- -> describe('name field', () => { -> const helpers = { -> blockHelperMissing(...args: any[]) { -> return 'missing: ' + args[args.length - 1].name; -787,788c718,722 -< helperMissing: function() { -< return 'helper missing: ' + arguments[arguments.length - 1].name; ---- -> helperMissing(...args: any[]) { -> return 'helper missing: ' + args[args.length - 1].name; -> }, -> helper(...args: any[]) { -> return 'ran: ' + args[args.length - 1].name; -790,792d723 -< helper: function() { -< return 'ran: ' + arguments[arguments.length - 1].name; -< } -795,798c726,727 -< it('should include in ambiguous mustache calls', function() { -< expectTemplate('{{helper}}') -< .withHelpers(helpers) -< .toCompileTo('ran: helper'); ---- -> it('should include in ambiguous mustache calls', () => { -> expectTemplate('{{helper}}').withHelpers(helpers).toCompileTo('ran: helper'); -801,804c730,731 -< it('should include in helper mustache calls', function() { -< expectTemplate('{{helper 1}}') -< .withHelpers(helpers) -< .toCompileTo('ran: helper'); ---- -> it('should include in helper mustache calls', () => { -> expectTemplate('{{helper 1}}').withHelpers(helpers).toCompileTo('ran: helper'); -807,810c734,735 -< it('should include in ambiguous block calls', function() { -< expectTemplate('{{#helper}}{{/helper}}') -< .withHelpers(helpers) -< .toCompileTo('ran: helper'); ---- -> it('should include in ambiguous block calls', () => { -> expectTemplate('{{#helper}}{{/helper}}').withHelpers(helpers).toCompileTo('ran: helper'); -813c738 -< it('should include in simple block calls', function() { ---- -> it('should include in simple block calls', () => { -819,822c744,745 -< it('should include in helper block calls', function() { -< expectTemplate('{{#helper 1}}{{/helper}}') -< .withHelpers(helpers) -< .toCompileTo('ran: helper'); ---- -> it('should include in helper block calls', () => { -> expectTemplate('{{#helper 1}}{{/helper}}').withHelpers(helpers).toCompileTo('ran: helper'); -825c748 -< it('should include in known helper calls', function() { ---- -> it('should include in known helper calls', () => { -829c752 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -835c758 -< it('should include full id', function() { ---- -> it('should include full id', () => { -842c765 -< it('should include full id if a hash is passed', function() { ---- -> it('should include full id if a hash is passed', () => { -850,851c773,774 -< describe('name conflicts', function() { -< it('helpers take precedence over same-named context properties', function() { ---- -> describe('name conflicts', () => { -> it('helpers take precedence over same-named context properties', () => { -853c776 -< .withHelper('goodbye', function() { ---- -> .withHelper('goodbye', function (this: any) { -856c779 -< .withHelper('cruel', function(world) { ---- -> .withHelper('cruel', function (world) { -861c784 -< world: 'world' ---- -> world: 'world', -863d785 -< .withMessage('Helper executed') -867c789 -< it('helpers take precedence over same-named context properties$', function() { ---- -> it('helpers take precedence over same-named context properties$', () => { -869c791 -< .withHelper('goodbye', function(options) { ---- -> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) { -872c794 -< .withHelper('cruel', function(world) { ---- -> .withHelper('cruel', function (world) { -877c799 -< world: 'world' ---- -> world: 'world', -879d800 -< .withMessage('Helper executed') -883c804 -< it('Scoped names take precedence over helpers', function() { ---- -> it('Scoped names take precedence over helpers', () => { -885c806 -< .withHelper('goodbye', function() { ---- -> .withHelper('goodbye', function (this: any) { -888c809 -< .withHelper('cruel', function(world) { ---- -> .withHelper('cruel', function (world) { -893c814 -< world: 'world' ---- -> world: 'world', -895d815 -< .withMessage('Helper not executed') -899,903c819,821 -< it('Scoped names take precedence over block helpers', function() { -< expectTemplate( -< '{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}' -< ) -< .withHelper('goodbye', function(options) { ---- -> it('Scoped names take precedence over block helpers', () => { -> expectTemplate('{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}') -> .withHelper('goodbye', function (this: any, options: Handlebars.HelperOptions) { -906c824 -< .withHelper('cruel', function(world) { ---- -> .withHelper('cruel', function (world) { -911c829 -< world: 'world' ---- -> world: 'world', -913d830 -< .withMessage('Helper executed') -918,919c835,836 -< describe('block params', function() { -< it('should take presedence over context values', function() { ---- -> describe('block params', () => { -> it('should take presedence over context values', () => { -922,923c839,840 -< .withHelper('goodbyes', function(options) { -< equals(options.fn.blockParams, 1); ---- -> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) { -> expect(options.fn.blockParams).toEqual(1); -929c846 -< it('should take presedence over helper values', function() { ---- -> it('should take presedence over helper values', () => { -931c848 -< .withHelper('value', function() { ---- -> .withHelper('value', function () { -934,935c851,852 -< .withHelper('goodbyes', function(options) { -< equals(options.fn.blockParams, 1); ---- -> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) { -> expect(options.fn.blockParams).toEqual(1); -941,944c858,859 -< it('should not take presedence over pathed values', function() { -< expectTemplate( -< '{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}' -< ) ---- -> it('should not take presedence over pathed values', () => { -> expectTemplate('{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}') -946c861 -< .withHelper('value', function() { ---- -> .withHelper('value', function () { -949,950c864,865 -< .withHelper('goodbyes', function(options) { -< equals(options.fn.blockParams, 1); ---- -> .withHelper('goodbyes', function (this: any, options: Handlebars.HelperOptions) { -> expect(options.fn.blockParams).toEqual(1); -956,957c871,872 -< it('should take presednece over parent block params', function() { -< var value = 1; ---- -> it('should take presednece over parent block params', () => { -> let value: number; -959c874,879 -< '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}' ---- -> '{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}', -> { -> beforeEach() { -> value = 1; -> }, -> } -962c882 -< .withHelper('goodbyes', function(options) { ---- -> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) { -966,967c886 -< blockParams: -< options.fn.blockParams === 1 ? [value++, value++] : undefined ---- -> blockParams: options.fn.blockParams === 1 ? [value++, value++] : undefined, -974,977c893,894 -< it('should allow block params on chained helpers', function() { -< expectTemplate( -< '{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}' -< ) ---- -> it('should allow block params on chained helpers', () => { -> expectTemplate('{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}') -979,980c896,897 -< .withHelper('goodbyes', function(options) { -< equals(options.fn.blockParams, 1); ---- -> .withHelper('goodbyes', function (options: Handlebars.HelperOptions) { -> expect(options.fn.blockParams).toEqual(1); -987,991c904,906 -< describe('built-in helpers malformed arguments ', function() { -< it('if helper - too few arguments', function() { -< expectTemplate('{{#if}}{{/if}}').toThrow( -< /#if requires exactly one argument/ -< ); ---- -> describe('built-in helpers malformed arguments ', () => { -> it('if helper - too few arguments', () => { -> expectTemplate('{{#if}}{{/if}}').toThrow(/#if requires exactly one argument/); -994,997c909,910 -< it('if helper - too many arguments, string', function() { -< expectTemplate('{{#if test "string"}}{{/if}}').toThrow( -< /#if requires exactly one argument/ -< ); ---- -> it('if helper - too many arguments, string', () => { -> expectTemplate('{{#if test "string"}}{{/if}}').toThrow(/#if requires exactly one argument/); -1000,1003c913,914 -< it('if helper - too many arguments, undefined', function() { -< expectTemplate('{{#if test undefined}}{{/if}}').toThrow( -< /#if requires exactly one argument/ -< ); ---- -> it('if helper - too many arguments, undefined', () => { -> expectTemplate('{{#if test undefined}}{{/if}}').toThrow(/#if requires exactly one argument/); -1006,1009c917,918 -< it('if helper - too many arguments, null', function() { -< expectTemplate('{{#if test null}}{{/if}}').toThrow( -< /#if requires exactly one argument/ -< ); ---- -> it('if helper - too many arguments, null', () => { -> expectTemplate('{{#if test null}}{{/if}}').toThrow(/#if requires exactly one argument/); -1012,1015c921,922 -< it('unless helper - too few arguments', function() { -< expectTemplate('{{#unless}}{{/unless}}').toThrow( -< /#unless requires exactly one argument/ -< ); ---- -> it('unless helper - too few arguments', () => { -> expectTemplate('{{#unless}}{{/unless}}').toThrow(/#unless requires exactly one argument/); -1018c925 -< it('unless helper - too many arguments', function() { ---- -> it('unless helper - too many arguments', () => { -1024,1027c931,932 -< it('with helper - too few arguments', function() { -< expectTemplate('{{#with}}{{/with}}').toThrow( -< /#with requires exactly one argument/ -< ); ---- -> it('with helper - too few arguments', () => { -> expectTemplate('{{#with}}{{/with}}').toThrow(/#with requires exactly one argument/); -1030c935 -< it('with helper - too many arguments', function() { ---- -> it('with helper - too many arguments', () => { -1037,1038c942,943 -< describe('the lookupProperty-option', function() { -< it('should be passed to custom helpers', function() { ---- -> describe('the lookupProperty-option', () => { -> it('should be passed to custom helpers', () => { -1040,1042c945,950 -< .withHelper('testHelper', function testHelper(options) { -< return options.lookupProperty(this, 'testProperty'); -< }) ---- -> .withHelper( -> 'testHelper', -> function testHelper(this: any, options: Handlebars.HelperOptions) { -> return options.lookupProperty(this, 'testProperty'); -> } -> ) -1047a956,961 -> -> function deleteAllKeys(obj: { [key: string]: any }) { -> for (const key of Object.keys(obj)) { -> delete obj[key]; -> } -> } diff --git a/packages/kbn-handlebars/.patches/regressions.patch b/packages/kbn-handlebars/.patches/regressions.patch deleted file mode 100644 index 89eb0d927403e..0000000000000 --- a/packages/kbn-handlebars/.patches/regressions.patch +++ /dev/null @@ -1,519 +0,0 @@ -1,2c1,12 -< describe('Regressions', function() { -< it('GH-94: Cannot read property of undefined', function() { ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('Regressions', () => { -> it('GH-94: Cannot read property of undefined', () => { -9,10c19,20 -< name: 'Charles Darwin' -< } ---- -> name: 'Charles Darwin', -> }, -13,15c23,25 -< title: 'Lazarillo de Tormes' -< } -< ] ---- -> title: 'Lazarillo de Tormes', -> }, -> ], -17d26 -< .withMessage('Renders without an undefined property error') -21,43c30,35 -< it("GH-150: Inverted sections print when they shouldn't", function() { -< var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; -< -< expectTemplate(string) -< .withMessage( -< "inverted sections run when property isn't present in context" -< ) -< .toCompileTo('not set :: '); -< -< expectTemplate(string) -< .withInput({ set: undefined }) -< .withMessage('inverted sections run when property is undefined') -< .toCompileTo('not set :: '); -< -< expectTemplate(string) -< .withInput({ set: false }) -< .withMessage('inverted sections run when property is false') -< .toCompileTo('not set :: '); -< -< expectTemplate(string) -< .withInput({ set: true }) -< .withMessage("inverted sections don't run when property is true") -< .toCompileTo(' :: set'); ---- -> it("GH-150: Inverted sections print when they shouldn't", () => { -> const string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}'; -> expectTemplate(string).toCompileTo('not set :: '); -> expectTemplate(string).withInput({ set: undefined }).toCompileTo('not set :: '); -> expectTemplate(string).withInput({ set: false }).toCompileTo('not set :: '); -> expectTemplate(string).withInput({ set: true }).toCompileTo(' :: set'); -46c38 -< it('GH-158: Using array index twice, breaks the template', function() { ---- -> it('GH-158: Using array index twice, breaks the template', () => { -49d40 -< .withMessage('it works as expected') -53,54c44,45 -< it("bug reported by @fat where lambdas weren't being properly resolved", function() { -< var string = ---- -> it("bug reported by @fat where lambdas weren't being properly resolved", () => { -> const string = -69,70c60,61 -< var data = { -< thing: function() { ---- -> const data = { -> thing() { -76c67 -< { className: 'three', word: '@sayrer' } ---- -> { className: 'three', word: '@sayrer' }, -78c69 -< hasThings: function() { ---- -> hasThings() { -80c71 -< } ---- -> }, -83c74 -< var output = ---- -> const output = -92,94c83 -< expectTemplate(string) -< .withInput(data) -< .toCompileTo(output); ---- -> expectTemplate(string).withInput(data).toCompileTo(output); -97,100c86,87 -< it('GH-408: Multiple loops fail', function() { -< expectTemplate( -< '{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}' -< ) ---- -> it('GH-408: Multiple loops fail', () => { -> expectTemplate('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}') -103c90 -< { name: 'Jane Doe', location: { city: 'New York' } } ---- -> { name: 'Jane Doe', location: { city: 'New York' } }, -105d91 -< .withMessage('It should output multiple times') -109,110c95,96 -< it('GS-428: Nested if else rendering', function() { -< var succeedingTemplate = ---- -> it('GS-428: Nested if else rendering', () => { -> const succeedingTemplate = -112c98 -< var failingTemplate = ---- -> const failingTemplate = -115,116c101,102 -< var helpers = { -< blk: function(block) { ---- -> const helpers = { -> blk(block: Handlebars.HelperOptions) { -119c105 -< inverse: function(block) { ---- -> inverse(block: Handlebars.HelperOptions) { -121c107 -< } ---- -> }, -124,130c110,111 -< expectTemplate(succeedingTemplate) -< .withHelpers(helpers) -< .toCompileTo(' Expected '); -< -< expectTemplate(failingTemplate) -< .withHelpers(helpers) -< .toCompileTo(' Expected '); ---- -> expectTemplate(succeedingTemplate).withHelpers(helpers).toCompileTo(' Expected '); -> expectTemplate(failingTemplate).withHelpers(helpers).toCompileTo(' Expected '); -133,136c114,115 -< it('GH-458: Scoped this identifier', function() { -< expectTemplate('{{./foo}}') -< .withInput({ foo: 'bar' }) -< .toCompileTo('bar'); ---- -> it('GH-458: Scoped this identifier', () => { -> expectTemplate('{{./foo}}').withInput({ foo: 'bar' }).toCompileTo('bar'); -139c118 -< it('GH-375: Unicode line terminators', function() { ---- -> it('GH-375: Unicode line terminators', () => { -143c122 -< it('GH-534: Object prototype aliases', function() { ---- -> it('GH-534: Object prototype aliases', () => { -144a124 -> // @ts-expect-error -147,149c127 -< expectTemplate('{{foo}}') -< .withInput({ foo: 'bar' }) -< .toCompileTo('bar'); ---- -> expectTemplate('{{foo}}').withInput({ foo: 'bar' }).toCompileTo('bar'); -150a129 -> // @ts-expect-error -155,157c134,136 -< it('GH-437: Matching escaping', function() { -< expectTemplate('{{{a}}').toThrow(Error, /Parse error on/); -< expectTemplate('{{a}}}').toThrow(Error, /Parse error on/); ---- -> it('GH-437: Matching escaping', () => { -> expectTemplate('{{{a}}').toThrow(/Parse error on/); -> expectTemplate('{{a}}}').toThrow(/Parse error on/); -160,166c139,141 -< it('GH-676: Using array in escaping mustache fails', function() { -< var data = { arr: [1, 2] }; -< -< expectTemplate('{{arr}}') -< .withInput(data) -< .withMessage('it works as expected') -< .toCompileTo(data.arr.toString()); ---- -> it('GH-676: Using array in escaping mustache fails', () => { -> const data = { arr: [1, 2] }; -> expectTemplate('{{arr}}').withInput(data).toCompileTo(data.arr.toString()); -169c144 -< it('Mustache man page', function() { ---- -> it('Mustache man page', () => { -177c152 -< in_ca: true ---- -> in_ca: true, -179,182c154 -< .withMessage('the hello world mustache example works') -< .toCompileTo( -< 'Hello Chris. You have just won $10000! Well, $6000, after taxes.' -< ); ---- -> .toCompileTo('Hello Chris. You have just won $10000! Well, $6000, after taxes.'); -185c157 -< it('GH-731: zero context rendering', function() { ---- -> it('GH-731: zero context rendering', () => { -189c161 -< bar: 'OK' ---- -> bar: 'OK', -194,197c166,167 -< it('GH-820: zero pathed rendering', function() { -< expectTemplate('{{foo.bar}}') -< .withInput({ foo: 0 }) -< .toCompileTo(''); ---- -> it('GH-820: zero pathed rendering', () => { -> expectTemplate('{{foo.bar}}').withInput({ foo: 0 }).toCompileTo(''); -200c170 -< it('GH-837: undefined values for helpers', function() { ---- -> it('GH-837: undefined values for helpers', () => { -203c173 -< str: function(value) { ---- -> str(value) { -205c175 -< } ---- -> }, -210c180 -< it('GH-926: Depths and de-dupe', function() { ---- -> it('GH-926: Depths and de-dupe', () => { -217c187 -< notData: [1] ---- -> notData: [1], -222c192 -< it('GH-1021: Each empty string key', function() { ---- -> it('GH-1021: Each empty string key', () => { -228,229c198,199 -< value: 10000 -< } ---- -> value: 10000, -> }, -234,248c204,205 -< it('GH-1054: Should handle simple safe string responses', function() { -< expectTemplate('{{#wrap}}{{>partial}}{{/wrap}}') -< .withHelpers({ -< wrap: function(options) { -< return new Handlebars.SafeString(options.fn()); -< } -< }) -< .withPartials({ -< partial: '{{#wrap}}{{/wrap}}' -< }) -< .toCompileTo(''); -< }); -< -< it('GH-1065: Sparse arrays', function() { -< var array = []; ---- -> it('GH-1065: Sparse arrays', () => { -> const array = []; -252c209 -< .withInput({ array: array }) ---- -> .withInput({ array }) -256c213 -< it('GH-1093: Undefined helper context', function() { ---- -> it('GH-1093: Undefined helper context', () => { -260c217 -< helper: function() { ---- -> helper(this: any) { -263c220 -< for (var name in this) { ---- -> for (const name in this) { -270c227 -< } ---- -> }, -275,306c232 -< it('should support multiple levels of inline partials', function() { -< expectTemplate( -< '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}' -< ) -< .withPartials({ -< doctype: 'doctype{{> content}}', -< layout: -< '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}' -< }) -< .toCompileTo('doctypelayoutsubcontent'); -< }); -< -< it('GH-1089: should support failover content in multiple levels of inline partials', function() { -< expectTemplate('{{#> layout}}{{/layout}}') -< .withPartials({ -< doctype: 'doctype{{> content}}', -< layout: -< '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}' -< }) -< .toCompileTo('doctypelayoutsubcontent'); -< }); -< -< it('GH-1099: should support greater than 3 nested levels of inline partials', function() { -< expectTemplate('{{#> layout}}Outer{{/layout}}') -< .withPartials({ -< layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}', -< inner: '' -< }) -< .toCompileTo('Outer'); -< }); -< -< it('GH-1135 : Context handling within each iteration', function() { ---- -> it('GH-1135 : Context handling within each iteration', () => { -315c241 -< myif: function(conditional, options) { ---- -> myif(conditional, options: Handlebars.HelperOptions) { -321c247 -< } ---- -> }, -326,343c252,253 -< it('GH-1186: Support block params for existing programs', function() { -< expectTemplate( -< '{{#*inline "test"}}{{> @partial-block }}{{/inline}}' + -< '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' + -< '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}' -< ) -< .withInput({ -< listOne: ['a'], -< listTwo: ['b'] -< }) -< .withMessage('') -< .toCompileTo('ab'); -< }); -< -< it('GH-1319: "unless" breaks when "each" value equals "null"', function() { -< expectTemplate( -< '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}' -< ) ---- -> it('GH-1319: "unless" breaks when "each" value equals "null"', () => { -> expectTemplate('{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}') -346c256 -< list: [null, 'a'] ---- -> list: [null, 'a'], -348d257 -< .withMessage('') -352,457c261 -< it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() { -< expectTemplate('template {{>partial}} template') -< .withPartials({ -< partialWithBlock: -< '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}', -< partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}' -< }) -< .toCompileTo('template block partial block template'); -< }); -< -< describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() { -< it('should compile and execute templates', function() { -< var newHandlebarsInstance = Handlebars.create(); -< -< registerTemplate(newHandlebarsInstance, compiledTemplateVersion7()); -< newHandlebarsInstance.registerHelper('loud', function(value) { -< return value.toUpperCase(); -< }); -< var result = newHandlebarsInstance.templates['test.hbs']({ -< name: 'yehuda' -< }); -< equals(result.trim(), 'YEHUDA'); -< }); -< -< it('should call "helperMissing" if a helper is missing', function() { -< var newHandlebarsInstance = Handlebars.create(); -< -< shouldThrow( -< function() { -< registerTemplate(newHandlebarsInstance, compiledTemplateVersion7()); -< newHandlebarsInstance.templates['test.hbs']({}); -< }, -< Handlebars.Exception, -< 'Missing helper: "loud"' -< ); -< }); -< -< it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function() { -< var newHandlebarsInstance = Handlebars.create(); -< registerTemplate( -< newHandlebarsInstance, -< compiledTemplateVersion7_usingLookupHelper() -< ); -< -< newHandlebarsInstance.templates['test.hbs']({}); -< -< expect( -< newHandlebarsInstance.templates['test.hbs']({ -< property: 'a', -< test: { a: 'b' } -< }) -< ).to.equal('b'); -< }); -< -< function registerTemplate(Handlebars, compileTemplate) { -< var template = Handlebars.template, -< templates = (Handlebars.templates = Handlebars.templates || {}); -< templates['test.hbs'] = template(compileTemplate); -< } -< -< function compiledTemplateVersion7() { -< return { -< compiler: [7, '>= 4.0.0'], -< main: function(container, depth0, helpers, partials, data) { -< return ( -< container.escapeExpression( -< ( -< helpers.loud || -< (depth0 && depth0.loud) || -< helpers.helperMissing -< ).call( -< depth0 != null ? depth0 : container.nullContext || {}, -< depth0 != null ? depth0.name : depth0, -< { name: 'loud', hash: {}, data: data } -< ) -< ) + '\n\n' -< ); -< }, -< useData: true -< }; -< } -< -< function compiledTemplateVersion7_usingLookupHelper() { -< // This is the compiled version of "{{lookup test property}}" -< return { -< compiler: [7, '>= 4.0.0'], -< main: function(container, depth0, helpers, partials, data) { -< return container.escapeExpression( -< helpers.lookup.call( -< depth0 != null ? depth0 : container.nullContext || {}, -< depth0 != null ? depth0.test : depth0, -< depth0 != null ? depth0.property : depth0, -< { -< name: 'lookup', -< hash: {}, -< data: data -< } -< ) -< ); -< }, -< useData: true -< }; -< } -< }); -< -< it('should allow hash with protected array names', function() { ---- -> it('should allow hash with protected array names', () => { -461c265 -< helpa: function(options) { ---- -> helpa(options: Handlebars.HelperOptions) { -463c267 -< } ---- -> }, -468,496c272,273 -< describe('GH-1598: Performance degradation for partials since v4.3.0', function() { -< // Do not run test for runs without compiler -< if (!Handlebars.compile) { -< return; -< } -< -< var newHandlebarsInstance; -< beforeEach(function() { -< newHandlebarsInstance = Handlebars.create(); -< }); -< afterEach(function() { -< sinon.restore(); -< }); -< -< it('should only compile global partials once', function() { -< var templateSpy = sinon.spy(newHandlebarsInstance, 'template'); -< newHandlebarsInstance.registerPartial({ -< dude: 'I am a partial' -< }); -< var string = 'Dudes: {{> dude}} {{> dude}}'; -< newHandlebarsInstance.compile(string)(); // This should compile template + partial once -< newHandlebarsInstance.compile(string)(); // This should only compile template -< equal(templateSpy.callCount, 3); -< sinon.restore(); -< }); -< }); -< -< describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", function() { -< it('should treat undefined helpers like non-existing helpers', function() { ---- -> describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", () => { -> it('should treat undefined helpers like non-existing helpers', () => { diff --git a/packages/kbn-handlebars/.patches/security.patch b/packages/kbn-handlebars/.patches/security.patch deleted file mode 100644 index 3eb55711c3f9c..0000000000000 --- a/packages/kbn-handlebars/.patches/security.patch +++ /dev/null @@ -1,443 +0,0 @@ -1,6c1,6 -< describe('security issues', function() { -< describe('GH-1495: Prevent Remote Code Execution via constructor', function() { -< it('should not allow constructors to be accessed', function() { -< expectTemplate('{{lookup (lookup this "constructor") "name"}}') -< .withInput({}) -< .toCompileTo(''); ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -8,10c8,15 -< expectTemplate('{{constructor.name}}') -< .withInput({}) -< .toCompileTo(''); ---- -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('security issues', () => { -> describe('GH-1495: Prevent Remote Code Execution via constructor', () => { -> it('should not allow constructors to be accessed', () => { -> expectTemplate('{{lookup (lookup this "constructor") "name"}}').withInput({}).toCompileTo(''); -> expectTemplate('{{constructor.name}}').withInput({}).toCompileTo(''); -13c18 -< it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() { ---- -> it('GH-1603: should not allow constructors to be accessed (lookup via toString)', () => { -16c21 -< .withHelper('list', function(element) { ---- -> .withHelper('list', function (element) { -22c27 -< it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() { ---- -> it('should allow the "constructor" property to be accessed if it is an "ownProperty"', () => { -32c37 -< it('should allow the "constructor" property to be accessed if it is an "own property"', function() { ---- -> it('should allow the "constructor" property to be accessed if it is an "own property"', () => { -39,45c44,46 -< describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() { -< if (!Handlebars.compile) { -< return; -< } -< -< describe('without the option "allowExplicitCallOfHelperMissing"', function() { -< it('should throw an exception when calling "{{helperMissing}}" ', function() { ---- -> describe('GH-1558: Prevent explicit call of helperMissing-helpers', () => { -> describe('without the option "allowExplicitCallOfHelperMissing"', () => { -> it('should throw an exception when calling "{{helperMissing}}" ', () => { -49c50 -< it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { ---- -> it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', () => { -53,56c54,57 -< it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { -< var functionCalls = []; -< expect(function() { -< var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); ---- -> it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', () => { -> const functionCalls = []; -> expect(() => { -> const template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); -58c59 -< fn: function() { ---- -> fn() { -60c61 -< } ---- -> }, -62,63c63,64 -< }).to.throw(Error); -< expect(functionCalls.length).to.equal(0); ---- -> }).toThrow(Error); -> expect(functionCalls.length).toEqual(0); -66c67 -< it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { ---- -> it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', () => { -69c70 -< fn: function() { ---- -> fn() { -71c72 -< } ---- -> }, -76,110d76 -< -< describe('with the option "allowCallsToHelperMissing" set to true', function() { -< it('should not throw an exception when calling "{{helperMissing}}" ', function() { -< var template = Handlebars.compile('{{helperMissing}}'); -< template({}, { allowCallsToHelperMissing: true }); -< }); -< -< it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() { -< var template = Handlebars.compile( -< '{{#helperMissing}}{{/helperMissing}}' -< ); -< template({}, { allowCallsToHelperMissing: true }); -< }); -< -< it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() { -< var functionCalls = []; -< var template = Handlebars.compile('{{blockHelperMissing "abc" .}}'); -< template( -< { -< fn: function() { -< functionCalls.push('called'); -< } -< }, -< { allowCallsToHelperMissing: true } -< ); -< equals(functionCalls.length, 1); -< }); -< -< it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() { -< var template = Handlebars.compile( -< '{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}' -< ); -< template({}, { allowCallsToHelperMissing: true }); -< }); -< }); -113,114c79,81 -< describe('GH-1563', function() { -< it('should not allow to access constructor after overriding via __defineGetter__', function() { ---- -> describe('GH-1563', () => { -> it('should not allow to access constructor after overriding via __defineGetter__', () => { -> // @ts-expect-error -116c83 -< return this.skip(); // Browser does not support this exploit anyway ---- -> return; // Browser does not support this exploit anyway -130,131c97,98 -< describe('GH-1595: dangerous properties', function() { -< var templates = [ ---- -> describe('GH-1595: dangerous properties', () => { -> const templates = [ -141c108 -< '{{lookup this "__proto__"}}' ---- -> '{{lookup this "__proto__"}}', -144,382c111,114 -< templates.forEach(function(template) { -< describe('access should be denied to ' + template, function() { -< it('by default', function() { -< expectTemplate(template) -< .withInput({}) -< .toCompileTo(''); -< }); -< it(' with proto-access enabled', function() { -< expectTemplate(template) -< .withInput({}) -< .withRuntimeOptions({ -< allowProtoPropertiesByDefault: true, -< allowProtoMethodsByDefault: true -< }) -< .toCompileTo(''); -< }); -< }); -< }); -< }); -< describe('GH-1631: disallow access to prototype functions', function() { -< function TestClass() {} -< -< TestClass.prototype.aProperty = 'propertyValue'; -< TestClass.prototype.aMethod = function() { -< return 'returnValue'; -< }; -< -< beforeEach(function() { -< handlebarsEnv.resetLoggedPropertyAccesses(); -< }); -< -< afterEach(function() { -< sinon.restore(); -< }); -< -< describe('control access to prototype methods via "allowedProtoMethods"', function() { -< checkProtoMethodAccess({}); -< -< describe('in compat mode', function() { -< checkProtoMethodAccess({ compat: true }); -< }); -< -< function checkProtoMethodAccess(compileOptions) { -< it('should be prohibited by default and log a warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .toCompileTo(''); -< -< expect(spy.calledOnce).to.be.true(); -< expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); -< }); -< -< it('should only log the warning once', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .toCompileTo(''); -< -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .toCompileTo(''); -< -< expect(spy.calledOnce).to.be.true(); -< expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); -< }); -< -< it('can be allowed, which disables the warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowedProtoMethods: { -< aMethod: true -< } -< }) -< .toCompileTo('returnValue'); -< -< expect(spy.callCount).to.equal(0); -< }); -< -< it('can be turned on by default, which disables the warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowProtoMethodsByDefault: true -< }) -< .toCompileTo('returnValue'); -< -< expect(spy.callCount).to.equal(0); -< }); -< -< it('can be turned off by default, which disables the warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowProtoMethodsByDefault: false -< }) -< .toCompileTo(''); -< -< expect(spy.callCount).to.equal(0); -< }); -< -< it('can be turned off, if turned on by default', function() { -< expectTemplate('{{aMethod}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowProtoMethodsByDefault: true, -< allowedProtoMethods: { -< aMethod: false -< } -< }) -< .toCompileTo(''); -< }); -< } -< -< it('should cause the recursive lookup by default (in "compat" mode)', function() { -< expectTemplate('{{#aString}}{{trim}}{{/aString}}') -< .withInput({ aString: ' abc ', trim: 'trim' }) -< .withCompileOptions({ compat: true }) -< .toCompileTo('trim'); -< }); -< -< it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() { -< expectTemplate('{{#aString}}{{trim}}{{/aString}}') -< .withInput({ aString: ' abc ', trim: 'trim' }) -< .withCompileOptions({ compat: true }) -< .withRuntimeOptions({ -< allowedProtoMethods: { -< trim: true -< } -< }) -< .toCompileTo('abc'); -< }); -< }); -< -< describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function() { -< checkProtoPropertyAccess({}); -< -< describe('in compat-mode', function() { -< checkProtoPropertyAccess({ compat: true }); -< }); -< -< describe('in strict-mode', function() { -< checkProtoPropertyAccess({ strict: true }); -< }); -< -< function checkProtoPropertyAccess(compileOptions) { -< it('should be prohibited by default and log a warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aProperty}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .toCompileTo(''); -< -< expect(spy.calledOnce).to.be.true(); -< expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/); -< }); -< -< it('can be explicitly prohibited by default, which disables the warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aProperty}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowProtoPropertiesByDefault: false -< }) -< .toCompileTo(''); -< -< expect(spy.callCount).to.equal(0); -< }); -< -< it('can be turned on, which disables the warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aProperty}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowedProtoProperties: { -< aProperty: true -< } -< }) -< .toCompileTo('propertyValue'); -< -< expect(spy.callCount).to.equal(0); -< }); -< -< it('can be turned on by default, which disables the warning', function() { -< var spy = sinon.spy(console, 'error'); -< -< expectTemplate('{{aProperty}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowProtoPropertiesByDefault: true -< }) -< .toCompileTo('propertyValue'); -< -< expect(spy.callCount).to.equal(0); -< }); -< -< it('can be turned off, if turned on by default', function() { -< expectTemplate('{{aProperty}}') -< .withInput(new TestClass()) -< .withCompileOptions(compileOptions) -< .withRuntimeOptions({ -< allowProtoPropertiesByDefault: true, -< allowedProtoProperties: { -< aProperty: false -< } -< }) -< .toCompileTo(''); -< }); -< } -< }); -< -< describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() { -< beforeEach(function simulateRuntimeWithoutLookupProperty() { -< var oldTemplateMethod = handlebarsEnv.template; -< sinon.replace(handlebarsEnv, 'template', function(templateSpec) { -< templateSpec.main = wrapToAdjustContainer(templateSpec.main); -< return oldTemplateMethod.call(this, templateSpec); ---- -> templates.forEach((template) => { -> describe('access should be denied to ' + template, () => { -> it('by default', () => { -> expectTemplate(template).withInput({}).toCompileTo(''); -385,400d116 -< -< afterEach(function() { -< sinon.restore(); -< }); -< -< it('should work with simple properties', function() { -< expectTemplate('{{aProperty}}') -< .withInput({ aProperty: 'propertyValue' }) -< .toCompileTo('propertyValue'); -< }); -< -< it('should work with Array.prototype.length', function() { -< expectTemplate('{{anArray.length}}') -< .withInput({ anArray: ['a', 'b', 'c'] }) -< .toCompileTo('3'); -< }); -404,409c120,122 -< describe('escapes template variables', function() { -< it('in compat mode', function() { -< expectTemplate("{{'a\\b'}}") -< .withCompileOptions({ compat: true }) -< .withInput({ 'a\\b': 'c' }) -< .toCompileTo('c'); ---- -> describe('escapes template variables', () => { -> it('in default mode', () => { -> expectTemplate("{{'a\\b'}}").withCompileOptions().withInput({ 'a\\b': 'c' }).toCompileTo('c'); -412,418c125 -< it('in default mode', function() { -< expectTemplate("{{'a\\b'}}") -< .withCompileOptions() -< .withInput({ 'a\\b': 'c' }) -< .toCompileTo('c'); -< }); -< it('in default mode', function() { ---- -> it('in strict mode', () => { -426,432d132 -< -< function wrapToAdjustContainer(precompiledTemplateFunction) { -< return function templateFunctionWrapper(container /*, more args */) { -< delete container.lookupProperty; -< return precompiledTemplateFunction.apply(this, arguments); -< }; -< } diff --git a/packages/kbn-handlebars/.patches/strict.patch b/packages/kbn-handlebars/.patches/strict.patch deleted file mode 100644 index 30613348b9852..0000000000000 --- a/packages/kbn-handlebars/.patches/strict.patch +++ /dev/null @@ -1,180 +0,0 @@ -1c1,6 -< var Exception = Handlebars.Exception; ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -3,5c8,12 -< describe('strict', function() { -< describe('strict mode', function() { -< it('should error on missing property lookup', function() { ---- -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('strict', () => { -> describe('strict mode', () => { -> it('should error on missing property lookup', () => { -8c15 -< .toThrow(Exception, /"hello" not defined in/); ---- -> .toThrow(/"hello" not defined in/); -11c18 -< it('should error on missing child', function() { ---- -> it('should error on missing child', () => { -20c27 -< .toThrow(Exception, /"bar" not defined in/); ---- -> .toThrow(/"bar" not defined in/); -23c30 -< it('should handle explicit undefined', function() { ---- -> it('should handle explicit undefined', () => { -30c37 -< it('should error on missing property lookup in known helpers mode', function() { ---- -> it('should error on missing property lookup in known helpers mode', () => { -34c41 -< knownHelpersOnly: true ---- -> knownHelpersOnly: true, -36c43 -< .toThrow(Exception, /"hello" not defined in/); ---- -> .toThrow(/"hello" not defined in/); -39,42c46,47 -< it('should error on missing context', function() { -< expectTemplate('{{hello}}') -< .withCompileOptions({ strict: true }) -< .toThrow(Error); ---- -> it('should error on missing context', () => { -> expectTemplate('{{hello}}').withCompileOptions({ strict: true }).toThrow(Error); -45,47c50,52 -< it('should error on missing data lookup', function() { -< var xt = expectTemplate('{{@hello}}').withCompileOptions({ -< strict: true ---- -> it('should error on missing data lookup', () => { -> const xt = expectTemplate('{{@hello}}').withCompileOptions({ -> strict: true, -55c60 -< it('should not run helperMissing for helper calls', function() { ---- -> it('should not run helperMissing for helper calls', () => { -59c64 -< .toThrow(Exception, /"hello" not defined in/); ---- -> .toThrow(/"hello" not defined in/); -64c69 -< .toThrow(Exception, /"hello" not defined in/); ---- -> .toThrow(/"hello" not defined in/); -67c72 -< it('should throw on ambiguous blocks', function() { ---- -> it('should throw on ambiguous blocks', () => { -70c75 -< .toThrow(Exception, /"hello" not defined in/); ---- -> .toThrow(/"hello" not defined in/); -74c79 -< .toThrow(Exception, /"hello" not defined in/); ---- -> .toThrow(/"hello" not defined in/); -79c84 -< .toThrow(Exception, /"bar" not defined in/); ---- -> .toThrow(/"bar" not defined in/); -82c87 -< it('should allow undefined parameters when passed to helpers', function() { ---- -> it('should allow undefined parameters when passed to helpers', () => { -88c93 -< it('should allow undefined hash when passed to helpers', function() { ---- -> it('should allow undefined hash when passed to helpers', () => { -91c96 -< strict: true ---- -> strict: true, -94,96c99,101 -< helper: function(options) { -< equals('value' in options.hash, true); -< equals(options.hash.value, undefined); ---- -> helper(options) { -> expect('value' in options.hash).toEqual(true); -> expect(options.hash.value).toBeUndefined(); -98c103 -< } ---- -> }, -103c108 -< it('should show error location on missing property lookup', function() { ---- -> it('should show error location on missing property lookup', () => { -106c111 -< .toThrow(Exception, '"hello" not defined in [object Object] - 4:5'); ---- -> .toThrow('"hello" not defined in [object Object] - 4:5'); -109c114 -< it('should error contains correct location properties on missing property lookup', function() { ---- -> it('should error contains correct location properties on missing property lookup', () => { -111,114c116,118 -< var template = CompilerContext.compile('\n\n\n {{hello}}', { -< strict: true -< }); -< template({}); ---- -> expectTemplate('\n\n\n {{hello}}') -> .withCompileOptions({ strict: true }) -> .toCompileTo('throw before asserting this'); -116,119c120,123 -< equals(error.lineNumber, 4); -< equals(error.endLineNumber, 4); -< equals(error.column, 5); -< equals(error.endColumn, 10); ---- -> expect(error.lineNumber).toEqual(4); -> expect(error.endLineNumber).toEqual(4); -> expect(error.column).toEqual(5); -> expect(error.endColumn).toEqual(10); -124,128c128,130 -< describe('assume objects', function() { -< it('should ignore missing property', function() { -< expectTemplate('{{hello}}') -< .withCompileOptions({ assumeObjects: true }) -< .toCompileTo(''); ---- -> describe('assume objects', () => { -> it('should ignore missing property', () => { -> expectTemplate('{{hello}}').withCompileOptions({ assumeObjects: true }).toCompileTo(''); -131c133 -< it('should ignore missing child', function() { ---- -> it('should ignore missing child', () => { -138,141c140,141 -< it('should error on missing object', function() { -< expectTemplate('{{hello.bar}}') -< .withCompileOptions({ assumeObjects: true }) -< .toThrow(Error); ---- -> it('should error on missing object', () => { -> expectTemplate('{{hello.bar}}').withCompileOptions({ assumeObjects: true }).toThrow(Error); -144c144 -< it('should error on missing context', function() { ---- -> it('should error on missing context', () => { -151c151 -< it('should error on missing data lookup', function() { ---- -> it('should error on missing data lookup', () => { -158c158 -< it('should execute blockHelperMissing', function() { ---- -> it('should execute blockHelperMissing', () => { diff --git a/packages/kbn-handlebars/.patches/subexpressions.patch b/packages/kbn-handlebars/.patches/subexpressions.patch deleted file mode 100644 index c775ac2035aff..0000000000000 --- a/packages/kbn-handlebars/.patches/subexpressions.patch +++ /dev/null @@ -1,318 +0,0 @@ -1,2c1,12 -< describe('subexpressions', function() { -< it('arg-less helper', function() { ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -> -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -> -> describe('subexpressions', () => { -> it('arg-less helper', () => { -5c15 -< foo: function(val) { ---- -> foo(val) { -8c18 -< bar: function() { ---- -> bar() { -10c20 -< } ---- -> }, -15c25 -< it('helper w args', function() { ---- -> it('helper w args', () => { -19c29 -< blog: function(val) { ---- -> blog(val) { -22c32 -< equal: function(x, y) { ---- -> equal(x, y) { -24c34 -< } ---- -> }, -29c39 -< it('mixed paths and helpers', function() { ---- -> it('mixed paths and helpers', () => { -33c43 -< blog: function(val, that, theOther) { ---- -> blog(val, that, theOther) { -36c46 -< equal: function(x, y) { ---- -> equal(x, y) { -38c48 -< } ---- -> }, -43c53 -< it('supports much nesting', function() { ---- -> it('supports much nesting', () => { -47c57 -< blog: function(val) { ---- -> blog(val) { -50c60 -< equal: function(x, y) { ---- -> equal(x, y) { -52c62 -< } ---- -> }, -57,60c67,70 -< it('GH-800 : Complex subexpressions', function() { -< var context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } }; -< var helpers = { -< dash: function(a, b) { ---- -> it('GH-800 : Complex subexpressions', () => { -> const context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } }; -> const helpers = { -> dash(a: any, b: any) { -63c73 -< concat: function(a, b) { ---- -> concat(a: any, b: any) { -65c75 -< } ---- -> }, -94,97c104,107 -< it('provides each nested helper invocation its own options hash', function() { -< var lastOptions = null; -< var helpers = { -< equal: function(x, y, options) { ---- -> it('provides each nested helper invocation its own options hash', () => { -> let lastOptions: Handlebars.HelperOptions; -> const helpers = { -> equal(x: any, y: any, options: Handlebars.HelperOptions) { -103c113 -< } ---- -> }, -105,107c115 -< expectTemplate('{{equal (equal true true) true}}') -< .withHelpers(helpers) -< .toCompileTo('true'); ---- -> expectTemplate('{{equal (equal true true) true}}').withHelpers(helpers).toCompileTo('true'); -110c118 -< it('with hashes', function() { ---- -> it('with hashes', () => { -114c122 -< blog: function(val) { ---- -> blog(val) { -117c125 -< equal: function(x, y) { ---- -> equal(x, y) { -119c127 -< } ---- -> }, -124c132 -< it('as hashes', function() { ---- -> it('as hashes', () => { -127c135 -< blog: function(options) { ---- -> blog(options) { -130c138 -< equal: function(x, y) { ---- -> equal(x, y) { -132c140 -< } ---- -> }, -137,140c145,146 -< it('multiple subexpressions in a hash', function() { -< expectTemplate( -< '{{input aria-label=(t "Name") placeholder=(t "Example User")}}' -< ) ---- -> it('multiple subexpressions in a hash', () => { -> expectTemplate('{{input aria-label=(t "Name") placeholder=(t "Example User")}}') -142,145c148,151 -< input: function(options) { -< var hash = options.hash; -< var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); -< var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); ---- -> input(options) { -> const hash = options.hash; -> const ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); -> const placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); -147,151c153 -< '' ---- -> '' -154c156 -< t: function(defaultString) { ---- -> t(defaultString) { -156c158 -< } ---- -> }, -161,164c163,164 -< it('multiple subexpressions in a hash with context', function() { -< expectTemplate( -< '{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}' -< ) ---- -> it('multiple subexpressions in a hash with context', () => { -> expectTemplate('{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}') -168,169c168,169 -< placeholder: 'Example User' -< } ---- -> placeholder: 'Example User', -> }, -172,175c172,175 -< input: function(options) { -< var hash = options.hash; -< var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); -< var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); ---- -> input(options) { -> const hash = options.hash; -> const ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']); -> const placeholder = Handlebars.Utils.escapeExpression(hash.placeholder); -177,181c177 -< '' ---- -> '' -184c180 -< t: function(defaultString) { ---- -> t(defaultString) { -186,242d181 -< } -< }) -< .toCompileTo(''); -< }); -< -< it('in string params mode,', function() { -< expectTemplate('{{snog (blorg foo x=y) yeah a=b}}') -< .withCompileOptions({ stringParams: true }) -< .withHelpers({ -< snog: function(a, b, options) { -< equals(a, 'foo'); -< equals( -< options.types.length, -< 2, -< 'string params for outer helper processed correctly' -< ); -< equals( -< options.types[0], -< 'SubExpression', -< 'string params for outer helper processed correctly' -< ); -< equals( -< options.types[1], -< 'PathExpression', -< 'string params for outer helper processed correctly' -< ); -< return a + b; -< }, -< -< blorg: function(a, options) { -< equals( -< options.types.length, -< 1, -< 'string params for inner helper processed correctly' -< ); -< equals( -< options.types[0], -< 'PathExpression', -< 'string params for inner helper processed correctly' -< ); -< return a; -< } -< }) -< .withInput({ -< foo: {}, -< yeah: {} -< }) -< .toCompileTo('fooyeah'); -< }); -< -< it('as hashes in string params mode', function() { -< expectTemplate('{{blog fun=(bork)}}') -< .withCompileOptions({ stringParams: true }) -< .withHelpers({ -< blog: function(options) { -< equals(options.hashTypes.fun, 'SubExpression'); -< return 'val is ' + options.hash.fun; -244,246d182 -< bork: function() { -< return 'BORK'; -< } -248c184 -< .toCompileTo('val is BORK'); ---- -> .toCompileTo(''); -251c187 -< it('subexpression functions on the context', function() { ---- -> it('subexpression functions on the context', () => { -254c190 -< bar: function() { ---- -> bar() { -256c192 -< } ---- -> }, -259c195 -< foo: function(val) { ---- -> foo(val) { -261c197 -< } ---- -> }, -266c202 -< it("subexpressions can't just be property lookups", function() { ---- -> it("subexpressions can't just be property lookups", () => { -269c205 -< bar: 'LOL' ---- -> bar: 'LOL', -272c208 -< foo: function(val) { ---- -> foo(val) { -274c210 -< } ---- -> }, diff --git a/packages/kbn-handlebars/.patches/utils.patch b/packages/kbn-handlebars/.patches/utils.patch deleted file mode 100644 index 8bd09ad0c9927..0000000000000 --- a/packages/kbn-handlebars/.patches/utils.patch +++ /dev/null @@ -1,108 +0,0 @@ -1,55c1,6 -< describe('utils', function() { -< describe('#SafeString', function() { -< it('constructing a safestring from a string and checking its type', function() { -< var safe = new Handlebars.SafeString('testing 1, 2, 3'); -< if (!(safe instanceof Handlebars.SafeString)) { -< throw new Error('Must be instance of SafeString'); -< } -< equals( -< safe.toString(), -< 'testing 1, 2, 3', -< 'SafeString is equivalent to its underlying string' -< ); -< }); -< -< it('it should not escape SafeString properties', function() { -< var name = new Handlebars.SafeString('Sean O'Malley'); -< -< expectTemplate('{{name}}') -< .withInput({ name: name }) -< .toCompileTo('Sean O'Malley'); -< }); -< }); -< -< describe('#escapeExpression', function() { -< it('shouhld escape html', function() { -< equals( -< Handlebars.Utils.escapeExpression('foo<&"\'>'), -< 'foo<&"'>' -< ); -< equals(Handlebars.Utils.escapeExpression('foo='), 'foo='); -< }); -< it('should not escape SafeString', function() { -< var string = new Handlebars.SafeString('foo<&"\'>'); -< equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>'); -< -< var obj = { -< toHTML: function() { -< return 'foo<&"\'>'; -< } -< }; -< equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>'); -< }); -< it('should handle falsy', function() { -< equals(Handlebars.Utils.escapeExpression(''), ''); -< equals(Handlebars.Utils.escapeExpression(undefined), ''); -< equals(Handlebars.Utils.escapeExpression(null), ''); -< -< equals(Handlebars.Utils.escapeExpression(false), 'false'); -< equals(Handlebars.Utils.escapeExpression(0), '0'); -< }); -< it('should handle empty objects', function() { -< equals(Handlebars.Utils.escapeExpression({}), {}.toString()); -< equals(Handlebars.Utils.escapeExpression([]), [].toString()); -< }); -< }); ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -57,64c8,9 -< describe('#isEmpty', function() { -< it('should not be empty', function() { -< equals(Handlebars.Utils.isEmpty(undefined), true); -< equals(Handlebars.Utils.isEmpty(null), true); -< equals(Handlebars.Utils.isEmpty(false), true); -< equals(Handlebars.Utils.isEmpty(''), true); -< equals(Handlebars.Utils.isEmpty([]), true); -< }); ---- -> import Handlebars from '../..'; -> import { expectTemplate } from '../__jest__/test_bench'; -66,70c11,16 -< it('should be empty', function() { -< equals(Handlebars.Utils.isEmpty(0), false); -< equals(Handlebars.Utils.isEmpty([1]), false); -< equals(Handlebars.Utils.isEmpty('foo'), false); -< equals(Handlebars.Utils.isEmpty({ bar: 1 }), false); ---- -> describe('utils', function () { -> describe('#SafeString', function () { -> it('constructing a safestring from a string and checking its type', function () { -> const safe = new Handlebars.SafeString('testing 1, 2, 3'); -> expect(safe).toBeInstanceOf(Handlebars.SafeString); -> expect(safe.toString()).toEqual('testing 1, 2, 3'); -72,83d17 -< }); -< -< describe('#extend', function() { -< it('should ignore prototype values', function() { -< function A() { -< this.a = 1; -< } -< A.prototype.b = 4; -< -< var b = { b: 2 }; -< -< Handlebars.Utils.extend(b, new A()); -85,86c19,21 -< equals(b.a, 1); -< equals(b.b, 2); ---- -> it('it should not escape SafeString properties', function () { -> const name = new Handlebars.SafeString('Sean O'Malley'); -> expectTemplate('{{name}}').withInput({ name }).toCompileTo('Sean O'Malley'); diff --git a/packages/kbn-handlebars/.patches/whitespace-control.patch b/packages/kbn-handlebars/.patches/whitespace-control.patch deleted file mode 100644 index f973b0b5ad8d8..0000000000000 --- a/packages/kbn-handlebars/.patches/whitespace-control.patch +++ /dev/null @@ -1,186 +0,0 @@ -1,19c1,6 -< describe('whitespace control', function() { -< it('should strip whitespace around mustache calls', function() { -< var hash = { foo: 'bar<' }; -< -< expectTemplate(' {{~foo~}} ') -< .withInput(hash) -< .toCompileTo('bar<'); -< -< expectTemplate(' {{~foo}} ') -< .withInput(hash) -< .toCompileTo('bar< '); -< -< expectTemplate(' {{foo~}} ') -< .withInput(hash) -< .toCompileTo(' bar<'); -< -< expectTemplate(' {{~&foo~}} ') -< .withInput(hash) -< .toCompileTo('bar<'); ---- -> /* -> * This file is forked from the handlebars project (https://github.com/handlebars-lang/handlebars.js), -> * and may include modifications made by Elasticsearch B.V. -> * Elasticsearch B.V. licenses this file to you under the MIT License. -> * See `packages/kbn-handlebars/LICENSE` for more information. -> */ -21,23c8 -< expectTemplate(' {{~{foo}~}} ') -< .withInput(hash) -< .toCompileTo('bar<'); ---- -> import { expectTemplate } from '../__jest__/test_bench'; -24a10,17 -> describe('whitespace control', () => { -> it('should strip whitespace around mustache calls', () => { -> const hash = { foo: 'bar<' }; -> expectTemplate(' {{~foo~}} ').withInput(hash).toCompileTo('bar<'); -> expectTemplate(' {{~foo}} ').withInput(hash).toCompileTo('bar< '); -> expectTemplate(' {{foo~}} ').withInput(hash).toCompileTo(' bar<'); -> expectTemplate(' {{~&foo~}} ').withInput(hash).toCompileTo('bar<'); -> expectTemplate(' {{~{foo}~}} ').withInput(hash).toCompileTo('bar<'); -28,42c21,23 -< describe('blocks', function() { -< it('should strip whitespace around simple block calls', function() { -< var hash = { foo: 'bar<' }; -< -< expectTemplate(' {{~#if foo~}} bar {{~/if~}} ') -< .withInput(hash) -< .toCompileTo('bar'); -< -< expectTemplate(' {{#if foo~}} bar {{/if~}} ') -< .withInput(hash) -< .toCompileTo(' bar '); -< -< expectTemplate(' {{~#if foo}} bar {{~/if}} ') -< .withInput(hash) -< .toCompileTo(' bar '); ---- -> describe('blocks', () => { -> it('should strip whitespace around simple block calls', () => { -> const hash = { foo: 'bar<' }; -44,46c25,28 -< expectTemplate(' {{#if foo}} bar {{/if}} ') -< .withInput(hash) -< .toCompileTo(' bar '); ---- -> expectTemplate(' {{~#if foo~}} bar {{~/if~}} ').withInput(hash).toCompileTo('bar'); -> expectTemplate(' {{#if foo~}} bar {{/if~}} ').withInput(hash).toCompileTo(' bar '); -> expectTemplate(' {{~#if foo}} bar {{~/if}} ').withInput(hash).toCompileTo(' bar '); -> expectTemplate(' {{#if foo}} bar {{/if}} ').withInput(hash).toCompileTo(' bar '); -57c39 -< it('should strip whitespace around inverse block calls', function() { ---- -> it('should strip whitespace around inverse block calls', () => { -59d40 -< -61d41 -< -63d42 -< -65,68c44 -< -< expectTemplate( -< ' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ' -< ).toCompileTo('bar'); ---- -> expectTemplate(' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ').toCompileTo('bar'); -71,88c47,48 -< it('should strip whitespace around complex block calls', function() { -< var hash = { foo: 'bar<' }; -< -< expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}') -< .withInput(hash) -< .toCompileTo('bar'); -< -< expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}') -< .withInput(hash) -< .toCompileTo('bar '); -< -< expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}') -< .withInput(hash) -< .toCompileTo(' bar'); -< -< expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}') -< .withInput(hash) -< .toCompileTo(' bar '); ---- -> it('should strip whitespace around complex block calls', () => { -> const hash = { foo: 'bar<' }; -90,92c50,54 -< expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}') -< .withInput(hash) -< .toCompileTo('bar'); ---- -> expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); -> expectTemplate('{{#if foo~}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo('bar '); -> expectTemplate('{{#if foo}} bar {{~^~}} baz {{~/if}}').withInput(hash).toCompileTo(' bar'); -> expectTemplate('{{#if foo}} bar {{^~}} baz {{/if}}').withInput(hash).toCompileTo(' bar '); -> expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').withInput(hash).toCompileTo('bar'); -94,96c56 -< expectTemplate( -< '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' -< ) ---- -> expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') -100,102c60 -< expectTemplate( -< '\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' -< ) ---- -> expectTemplate('\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n') -106,109c64 -< expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo( -< 'baz' -< ); -< ---- -> expectTemplate('{{#if foo~}} bar {{~^~}} baz {{~/if}}').toCompileTo('baz'); -111,120c66,69 -< -< expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo( -< ' baz' -< ); -< -< expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo( -< ' baz ' -< ); -< -< expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo( ---- -> expectTemplate('{{#if foo~}} bar {{~^}} baz {{~/if}}').toCompileTo(' baz'); -> expectTemplate('{{#if foo~}} bar {{~^}} baz {{/if}}').toCompileTo(' baz '); -> expectTemplate('{{#if foo~}} bar {{~else~}} baz {{~/if}}').toCompileTo('baz'); -> expectTemplate('\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n').toCompileTo( -123,126d71 -< -< expectTemplate( -< '\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n' -< ).toCompileTo('baz'); -130,152c75 -< it('should strip whitespace around partials', function() { -< expectTemplate('foo {{~> dude~}} ') -< .withPartials({ dude: 'bar' }) -< .toCompileTo('foobar'); -< -< expectTemplate('foo {{> dude~}} ') -< .withPartials({ dude: 'bar' }) -< .toCompileTo('foo bar'); -< -< expectTemplate('foo {{> dude}} ') -< .withPartials({ dude: 'bar' }) -< .toCompileTo('foo bar '); -< -< expectTemplate('foo\n {{~> dude}} ') -< .withPartials({ dude: 'bar' }) -< .toCompileTo('foobar'); -< -< expectTemplate('foo\n {{> dude}} ') -< .withPartials({ dude: 'bar' }) -< .toCompileTo('foo\n bar'); -< }); -< -< it('should only strip whitespace once', function() { ---- -> it('should only strip whitespace once', () => { diff --git a/packages/kbn-handlebars/README.md b/packages/kbn-handlebars/README.md index 69fdfcd83033a..8528d9803b16d 100644 --- a/packages/kbn-handlebars/README.md +++ b/packages/kbn-handlebars/README.md @@ -77,25 +77,21 @@ By default, each test will run both the original `handlebars` code and the modif ## Development -Some of the tests have been copied from the upstream `handlebars` project and modified to fit our use-case, test-suite, and coding conventions. They are all located under the `packages/kbn-handlebars/src/upstream` directory. To check if any of the copied files have received updates upstream that we might want to include in our copies, you can run the following script: +Some of the tests have been copied from the upstream `handlebars` project and modified to fit our use-case, test-suite, and coding conventions. They are all located under the `packages/kbn-handlebars/src/spec` directory. To check if any of the copied files have received updates upstream that we might want to include in our copies, you can run the following script: ```sh -./packages/kbn-handlebars/scripts/check_for_test_changes.sh +./packages/kbn-handlebars/scripts/check_for_upstream_updates.sh ``` -If the script outputs a diff for a given file, it means that this file has been updated. - -_Note: that this will look for changes in the `4.x` branch of the `handlebars.js` repo only. Changes in the `master` branch are ignored._ +_Note: This will look for changes in the `4.x` branch of the `handlebars.js` repo only. Changes in the `master` branch are ignored._ Once all updates have been manually merged with our versions of the files, run the following script to "lock" us into the new updates: ```sh -./packages/kbn-handlebars/scripts/update_test_patches.sh +./packages/kbn-handlebars/scripts/update_upstream_git_hash.sh ``` -This will update the `.patch` files inside the `packages/kbn-handlebars/.patches` directory. Make sure to commit those changes. - -_Note: If we manually make changes to our test files in the `upstream` directory, we need to run the `update_test_patches.sh` script as well._ +This will update file `packages/kbn-handlebars/src/spec/.upstream_git_hash`. Make sure to commit changes to this file as well. ## Debugging diff --git a/packages/kbn-handlebars/index.test.ts b/packages/kbn-handlebars/index.test.ts index 6159a65dbcb8f..7a3a2a5772c14 100644 --- a/packages/kbn-handlebars/index.test.ts +++ b/packages/kbn-handlebars/index.test.ts @@ -307,7 +307,37 @@ describe('blocks', () => { .toCompileTo(''); }); - it('should pass expected options to root decorator', () => { + it('should pass expected options to root decorator with no args', () => { + expectTemplate('{{*decorator}}') + .withDecorator('decorator', function (fn, props, container, options) { + expect(options).toMatchInlineSnapshot(` + Object { + "args": Array [], + "data": Object { + "root": Object { + "foo": "bar", + }, + }, + "hash": Object {}, + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "name": "decorator", + } + `); + }) + .withInput({ foo: 'bar' }) + .toCompileTo(''); + }); + + it('should pass expected options to root decorator with one arg', () => { expectTemplate('{{*decorator foo}}') .withDecorator('decorator', function (fn, props, container, options) { expect(options).toMatchInlineSnapshot(` @@ -339,6 +369,163 @@ describe('blocks', () => { .toCompileTo(''); }); + describe('return values', () => { + for (const [desc, template, result] of [ + ['non-block', '{{*decorator}}cont{{*decorator}}ent', 'content'], + ['block', '{{#*decorator}}con{{/decorator}}tent', 'tent'], + ]) { + describe(desc, () => { + const falsy = [undefined, null, false, 0, '']; + const truthy = [true, 42, 'foo', {}]; + + // Falsy return values from decorators are simply ignored and the + // execution falls back to default behavior which is to render the + // other parts of the template. + for (const value of falsy) { + it(`falsy value (type ${typeof value}): ${JSON.stringify(value)}`, () => { + expectTemplate(template) + .withDecorator('decorator', () => value) + .toCompileTo(result); + }); + } + + // Truthy return values from decorators are expected to be functions + // and the program will attempt to call them. We expect an error to + // be thrown in this case. + for (const value of truthy) { + it(`non-falsy value (type ${typeof value}): ${JSON.stringify(value)}`, () => { + expectTemplate(template) + .withDecorator('decorator', () => value) + .toThrow('is not a function'); + }); + } + + // If the decorator return value is a custom function, its return + // value will be the final content of the template. + for (const value of [...falsy, ...truthy]) { + it(`function returning ${typeof value}: ${JSON.stringify(value)}`, () => { + expectTemplate(template) + .withDecorator('decorator', () => () => value) + .toCompileTo(value as string); + }); + } + }); + } + }); + + describe('custom return function should be called with expected arguments and its return value should be rendered in the template', () => { + it('root decorator', () => { + expectTemplate('{{*decorator}}world') + .withInput({ me: 'my' }) + .withDecorator( + 'decorator', + (fn): Handlebars.TemplateDelegate => + (context, options) => { + expect(context).toMatchInlineSnapshot(` + Object { + "me": "my", + } + `); + expect(options).toMatchInlineSnapshot(` + Object { + "decorators": Object { + "decorator": [Function], + }, + "helpers": Object {}, + } + `); + return `hello ${context.me} ${fn()}!`; + } + ) + .toCompileTo('hello my world!'); + }); + + it('decorator nested inside of array-helper', () => { + expectTemplate('{{#arr}}{{*decorator}}world{{/arr}}') + .withInput({ arr: ['my'] }) + .withDecorator( + 'decorator', + (fn): Handlebars.TemplateDelegate => + (context, options) => { + expect(context).toMatchInlineSnapshot(`"my"`); + expect(options).toMatchInlineSnapshot(` + Object { + "blockParams": Array [ + "my", + 0, + ], + "data": Object { + "_parent": Object { + "root": Object { + "arr": Array [ + "my", + ], + }, + }, + "first": true, + "index": 0, + "key": 0, + "last": true, + "root": Object { + "arr": Array [ + "my", + ], + }, + }, + } + `); + return `hello ${context} ${fn()}!`; + } + ) + .toCompileTo('hello my world!'); + }); + + it('decorator nested inside of custom helper', () => { + expectTemplate('{{#helper}}{{*decorator}}world{{/helper}}') + .withHelper('helper', function (options: Handlebars.HelperOptions) { + return options.fn('my', { foo: 'bar' } as any); + }) + .withDecorator( + 'decorator', + (fn): Handlebars.TemplateDelegate => + (context, options) => { + expect(context).toMatchInlineSnapshot(`"my"`); + expect(options).toMatchInlineSnapshot(` + Object { + "foo": "bar", + } + `); + return `hello ${context} ${fn()}!`; + } + ) + .toCompileTo('hello my world!'); + }); + }); + + it('should call multiple decorators in the same program body in the expected order and get the expected output', () => { + let decoratorCall = 0; + let progCall = 0; + expectTemplate('{{*decorator}}con{{*decorator}}tent') + .beforeRender(() => { + // ensure the counters are reset between EVAL/AST render calls + decoratorCall = 0; + progCall = 0; + }) + .withInput({ + decoratorCall: 0, + progCall: 0, + }) + .withDecorator('decorator', (fn) => { + const decoratorCallOrder = ++decoratorCall; + const ret: Handlebars.TemplateDelegate = () => { + const progCallOrder = ++progCall; + return `(decorator: ${decoratorCallOrder}, prog: ${progCallOrder}, fn: "${fn()}")`; + }; + return ret; + }) + .toCompileTo('(decorator: 2, prog: 1, fn: "(decorator: 1, prog: 2, fn: "content")")'); + }); + describe('registration', () => { beforeEach(() => { global.kbnHandlebarsEnv = Handlebars.create(); diff --git a/packages/kbn-handlebars/index.ts b/packages/kbn-handlebars/index.ts index da48f5e1475f7..2c63e014a3387 100644 --- a/packages/kbn-handlebars/index.ts +++ b/packages/kbn-handlebars/index.ts @@ -64,6 +64,13 @@ type ProcessableNodeWithPathPartsOrLiteral = ProcessableNode & { path: hbs.AST.PathExpression | hbs.AST.Literal; }; +interface Helper { + fn?: Handlebars.HelperDelegate; + context: any[]; + params: any[]; + options: AmbiguousHelperOptions; +} + export type NonBlockHelperOptions = Omit; export type AmbiguousHelperOptions = Handlebars.HelperOptions | NonBlockHelperOptions; @@ -290,7 +297,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { render(context: any, options: ExtendedRuntimeOptions = {}): string { this.contexts = [context]; this.output = []; - this.runtimeOptions = options; + this.runtimeOptions = Object.assign({}, options); this.container.helpers = Object.assign(this.initialHelpers, options.helpers); this.container.decorators = Object.assign( this.initialDecorators, @@ -312,9 +319,46 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { this.ast = Handlebars.parse(this.template!); } - this.accept(this.ast); + // The `defaultMain` function contains the default behavior: + // + // Generate a "program" function based on the root `Program` in the AST and + // call it. This will start the processing of all the child nodes in the + // AST. + const defaultMain: Handlebars.TemplateDelegate = (_context) => { + const prog = this.generateProgramFunction(this.ast!); + return prog(_context, this.runtimeOptions); + }; - return this.output.join(''); + // Run any decorators that might exist on the root: + // + // The `defaultMain` function is passed in, and if there are no root + // decorators, or if the decorators chooses to do so, the same function is + // returned from `processDecorators` and the default behavior is retained. + // + // Alternatively any of the root decorators might call the `defaultMain` + // function themselves, process its return value, and return a completely + // different `main` function. + const main = this.processDecorators(this.ast, defaultMain); + this.processedRootDecorators = true; + + // Call the `main` function and add the result to the final output. + const result = main(this.context, options); + + if (main === defaultMain) { + this.output.push(result); + return this.output.join(''); + } else { + // We normally expect the return value of `main` to be a string. However, + // if a decorator is used to override the `defaultMain` function, the + // return value can be any type. To match the upstream handlebars project + // behavior, we want the result of rendering the template to be the + // literal value returned by the decorator. + // + // Since the output array in this case always will be empty, we just + // return that single value instead of attempting to join all the array + // elements as strings. + return result; + } } // ********************************************** // @@ -323,11 +367,6 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { Program(program: hbs.AST.Program) { this.blockParamNames.unshift(program.blockParams); - - // Run any decorators that might exist on the root - this.processDecorators(program, this.generateProgramFunction(program)); - this.processedRootDecorators = true; - super.Program(program); this.blockParamNames.shift(); } @@ -340,14 +379,14 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { this.processStatementOrExpression(block); } - // This space intentionally left blank: We want to override the Visitor class implementation - // of this method, but since we handle decorators separately before traversing the nodes, we - // just want to make this a no-op. + // This space is intentionally left blank: We want to override the Visitor + // class implementation of this method, but since we handle decorators + // separately before traversing the nodes, we just want to make this a no-op. DecoratorBlock(decorator: hbs.AST.DecoratorBlock) {} - // This space intentionally left blank: We want to override the Visitor class implementation - // of this method, but since we handle decorators separately before traversing the nodes, we - // just want to make this a no-op. + // This space is intentionally left blank: We want to override the Visitor + // class implementation of this method, but since we handle decorators + // separately before traversing the nodes, we just want to make this a no-op. Decorator(decorator: hbs.AST.Decorator) {} SubExpression(sexpr: hbs.AST.SubExpression) { @@ -405,20 +444,23 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { */ private processDecorators(program: hbs.AST.Program, prog: Handlebars.TemplateDelegate) { if (!this.processedDecoratorsForProgram.has(program)) { + this.processedDecoratorsForProgram.add(program); + const props = {}; for (const node of program.body) { if (isDecorator(node)) { - this.processDecorator(node, prog); + prog = this.processDecorator(node, prog, props); } } - this.processedDecoratorsForProgram.add(program); } + + return prog; } private processDecorator( decorator: hbs.AST.DecoratorBlock | hbs.AST.Decorator, - prog: Handlebars.TemplateDelegate + prog: Handlebars.TemplateDelegate, + props: Record ) { - const props = {}; const options = this.setupDecoratorOptions(decorator); const result = this.container.lookupProperty( @@ -426,7 +468,7 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { options.name )(prog, props, this.container, options); - Object.assign(result || prog, props); + return Object.assign(result || prog, props); } private processStatementOrExpression(node: ProcessableNodeWithPathPartsOrLiteral) { @@ -590,77 +632,51 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { } private processAmbiguousNode(node: ProcessableNodeWithPathParts) { - const invokeResult = this.invokeAmbiguous(node); - - if (isBlock(node)) { - const result = this.ambiguousBlockValue(node, invokeResult); - if (result != null) { - this.output.push(result); - } - } else { - if ( - (node as hbs.AST.MustacheStatement).escaped === false || - this.compileOptions.noEscape === true || - typeof invokeResult !== 'string' - ) { - this.output.push(invokeResult); - } else { - this.output.push(Handlebars.escapeExpression(invokeResult)); - } - } - } - - // This operation is used when an expression like `{{foo}}` - // is provided, but we don't know at compile-time whether it - // is a helper or a path. - // - // This operation emits more code than the other options, - // and can be avoided by passing the `knownHelpers` and - // `knownHelpersOnly` flags at compile-time. - private invokeAmbiguous(node: ProcessableNodeWithPathParts) { const name = node.path.parts[0]; const helper = this.setupHelper(node, name); + let { fn: helperFn } = helper; - const loc = helper.fn ? node.loc : node.path.loc; - helper.fn = helper.fn ?? this.resolveNodes(node.path)[0]; + const loc = helperFn ? node.loc : node.path.loc; + helperFn = helperFn ?? this.resolveNodes(node.path)[0]; - if (helper.fn === undefined) { + if (helperFn === undefined) { if (this.compileOptions.strict) { - helper.fn = this.container.strict(helper.context, name, loc); + helperFn = this.container.strict(helper.context, name, loc); } else { - helper.fn = + helperFn = helper.context != null ? this.container.lookupProperty(helper.context, name) : helper.context; - if (helper.fn == null) helper.fn = this.container.hooks.helperMissing; + if (helperFn == null) helperFn = this.container.hooks.helperMissing; } } - return typeof helper.fn === 'function' - ? helper.fn.call(helper.context, ...helper.params, helper.options) - : helper.fn; - } - - private ambiguousBlockValue(block: hbs.AST.BlockStatement, value: any) { - const name = block.path.parts[0]; - const helper = this.setupHelper(block, name); + const helperResult = + typeof helperFn === 'function' + ? helperFn.call(helper.context, ...helper.params, helper.options) + : helperFn; - if (!helper.fn) { - value = this.container.hooks.blockHelperMissing!.call(this.context, value, helper.options); + if (isBlock(node)) { + const result = helper.fn + ? helperResult + : this.container.hooks.blockHelperMissing!.call(this.context, helperResult, helper.options); + if (result != null) { + this.output.push(result); + } + } else { + if ( + (node as hbs.AST.MustacheStatement).escaped === false || + this.compileOptions.noEscape === true || + typeof helperResult !== 'string' + ) { + this.output.push(helperResult); + } else { + this.output.push(Handlebars.escapeExpression(helperResult)); + } } - - return value; } - private setupHelper( - node: ProcessableNode, - helperName: string - ): { - fn?: Handlebars.HelperDelegate; - context: any[]; - params: any[]; - options: AmbiguousHelperOptions; - } { + private setupHelper(node: ProcessableNode, helperName: string): Helper { return { fn: this.container.lookupProperty(this.container.helpers, helperName), context: this.context, @@ -683,6 +699,8 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { } else { options.args = this.resolveNodes(decorator.params); } + } else { + options.args = []; } return options; @@ -701,13 +719,12 @@ class ElasticHandlebarsVisitor extends Handlebars.Visitor { }; if (isBlock(node)) { - // TODO: Is there a way in TypeScript to infer that `options` is `Handlebars.HelperOptions` inside this if-statement. If not, is there a way to just cast once? - (options as Handlebars.HelperOptions).fn = this.generateProgramFunction(node.program); - if (node.program) - this.processDecorators(node.program, (options as Handlebars.HelperOptions).fn); - (options as Handlebars.HelperOptions).inverse = this.generateProgramFunction(node.inverse); - if (node.inverse) - this.processDecorators(node.inverse, (options as Handlebars.HelperOptions).inverse); + (options as Handlebars.HelperOptions).fn = node.program + ? this.processDecorators(node.program, this.generateProgramFunction(node.program)) + : noop; + (options as Handlebars.HelperOptions).inverse = node.inverse + ? this.processDecorators(node.inverse, this.generateProgramFunction(node.inverse)) + : noop; } return options; diff --git a/packages/kbn-handlebars/scripts/check_for_test_changes.sh b/packages/kbn-handlebars/scripts/check_for_test_changes.sh deleted file mode 100755 index a9818505b707f..0000000000000 --- a/packages/kbn-handlebars/scripts/check_for_test_changes.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash - -set -e - -TMP=.tmp-handlebars - -# Try to detect Windows environment (I've not tested this!) -if [[ "$OSTYPE" == "msys" ]]; then - # Windows environment - DEVNULL=NUL -else - # Everything else (including Cygwin on Windows) - DEVNULL=/dev/null -fi - -function cleanup { - rm -fr $TMP -} - -trap cleanup EXIT - -rm -fr $TMP -mkdir $TMP - -echo "Cloning handlebars repo..." -git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP/handlebars - -files=(packages/kbn-handlebars/src/upstream/index.*.test.ts) - -for file in "${files[@]}" -do - tmp=${file#*.} # remove anything before first period - file=${tmp%.test.ts} # remove trailing .test.ts - - echo "Checking for changes to spec/$file.js..." - - set +e - diff -d --strip-trailing-cr $TMP/handlebars/spec/$file.js packages/kbn-handlebars/src/upstream/index.$file.test.ts > $TMP/$file.patch - error=$? - set -e - if [ $error -gt 1 ] - then - echo "The diff command encountered an unexpected error!" - exit $error - fi - - set +e - diff -d --strip-trailing-cr $TMP/$file.patch packages/kbn-handlebars/.patches/$file.patch > $DEVNULL - error=$? - set -e - if [ $error -gt 1 ] - then - echo "The diff command encountered an unexpected error!" - exit $error - elif [ $error -gt 0 ] - then - echo - echo "The following files contain unexpected differences:" - echo - echo " Upstream: spec/$file.js" - echo " Downstream: packages/kbn-handlebars/src/upstream/index.$file.test.ts" - echo - echo "This can happen if either the upstream or the downstream version has been" - echo "updated without our patch files being kept up to date." - echo - echo "To resolve this issue, do the following:" - echo - echo " 1. Check the '4.x' branch of the upstream git repository to see if the file" - echo " has been updated. If so, please ensure that our copy of the file is kept in" - echo " sync. You can view the recent upstream commits to this file here:" - echo - echo " https://github.com/handlebars-lang/handlebars.js/commits/4.x/spec/$file.js" - echo - echo " 2. Update our patch files by running the following script. This is also" - echo " necessary even if it's only the downstream file that has been updated:" - echo - echo " ./packages/kbn-handlebars/scripts/update_test_patches.sh $file" - echo - echo " 3. Commit the changes to the updated patch file and execute this script again" - echo " until everything passes:" - echo - echo " ./packages/kbn-handlebars/scripts/check_for_test_changes.sh" - echo - exit $error - fi -done - -echo "No changes found :)" diff --git a/packages/kbn-handlebars/scripts/check_for_upstream_updates.sh b/packages/kbn-handlebars/scripts/check_for_upstream_updates.sh new file mode 100755 index 0000000000000..73f7376ab4312 --- /dev/null +++ b/packages/kbn-handlebars/scripts/check_for_upstream_updates.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -e + +TMP=.tmp-handlebars +HASH_FILE=packages/kbn-handlebars/src/spec/.upstream_git_hash + +function cleanup { + rm -fr $TMP +} + +trap cleanup EXIT + +rm -fr $TMP +mkdir $TMP + +echo "Cloning handlebars repo..." +git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP + +echo "Looking for updates..." +hash=$(git -C $TMP rev-parse HEAD) +expected_hash=$(cat $HASH_FILE) + +if [ "$hash" = "$expected_hash" ]; then + echo "You're all up to date :)" +else + echo + echo "New changes has been committed to the '4.x' branch in the upstream git repository" + echo + echo "To resolve this issue, do the following:" + echo + echo " 1. Investigate the commits in the '4.x' branch of the upstream git repository." + echo " If files inside the 'spec' folder has been updated, sync those updates with" + echo " our local versions of these files (located in" + echo " 'packages/kbn-handlebars/src/spec')." + echo + echo " https://github.com/handlebars-lang/handlebars.js/compare/$hash...4.x" + echo + echo " 2. Execute the following script and commit the updated '$HASH_FILE'" + echo " file including any changes you made to our own spec files." + echo + echo " ./packages/kbn-handlebars/scripts/update_upstream_git_hash.sh" + echo + exit 1 +fi diff --git a/packages/kbn-handlebars/scripts/update_test_patches.sh b/packages/kbn-handlebars/scripts/update_test_patches.sh deleted file mode 100755 index 233ca9e0f25ba..0000000000000 --- a/packages/kbn-handlebars/scripts/update_test_patches.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -set -e - -TMP=.tmp-handlebars - -function cleanup { - rm -fr $TMP -} - -trap cleanup EXIT - -rm -fr $TMP -mkdir $TMP - -echo "Cloning handlebars repo..." -git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP/handlebars - -if [ -z "$1" ] -then - # No argument given: Update all patch files - files=(packages/kbn-handlebars/src/upstream/index.*.test.ts) -else - # Argument detected: Update only the requested patch file - files=(packages/kbn-handlebars/src/upstream/index.$1.test.ts) -fi - -for file in "${files[@]}" -do - tmp=${file#*.} # remove anything before first period - file=${tmp%.test.ts} # remove trailing .test.ts - - echo "Overwriting stored patch file for spec/$file.js..." - set +e - diff -d --strip-trailing-cr $TMP/handlebars/spec/$file.js packages/kbn-handlebars/src/upstream/index.$file.test.ts > packages/kbn-handlebars/.patches/$file.patch - set -e -done - -echo "All patches updated :)" diff --git a/packages/kbn-handlebars/scripts/update_upstream_git_hash.sh b/packages/kbn-handlebars/scripts/update_upstream_git_hash.sh new file mode 100755 index 0000000000000..52cc39e0d7bb3 --- /dev/null +++ b/packages/kbn-handlebars/scripts/update_upstream_git_hash.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -e + +TMP=.tmp-handlebars +HASH_FILE=packages/kbn-handlebars/src/spec/.upstream_git_hash + +function cleanup { + rm -fr $TMP +} + +trap cleanup EXIT + +rm -fr $TMP +mkdir $TMP + +echo "Cloning handlebars repo..." +git clone -q --depth 1 https://github.com/handlebars-lang/handlebars.js.git -b 4.x $TMP + +echo "Updating hash file..." +git -C $TMP rev-parse HEAD | tr -d '\n' > $HASH_FILE +git add $HASH_FILE + +echo "Done! - Don't forget to commit any changes to $HASH_FILE" diff --git a/packages/kbn-handlebars/src/__jest__/test_bench.ts b/packages/kbn-handlebars/src/__jest__/test_bench.ts index 4aaac2b52bd73..ffbf8b1fe84f5 100644 --- a/packages/kbn-handlebars/src/__jest__/test_bench.ts +++ b/packages/kbn-handlebars/src/__jest__/test_bench.ts @@ -38,6 +38,7 @@ export function forEachCompileFunctionName( class HandlebarsTestBench { private template: string; private options: TestOptions; + private beforeRenderFn: Function = () => {}; private compileOptions?: ExtendedCompileOptions; private runtimeOptions?: ExtendedRuntimeOptions; private helpers: { [name: string]: Handlebars.HelperDelegate | undefined } = {}; @@ -49,6 +50,11 @@ class HandlebarsTestBench { this.options = options; } + beforeRender(fn: Function) { + this.beforeRenderFn = fn; + return this; + } + withCompileOptions(compileOptions?: ExtendedCompileOptions) { this.compileOptions = compileOptions; return this; @@ -147,6 +153,8 @@ class HandlebarsTestBench { this.runtimeOptions ); + this.beforeRenderFn(); + return renderEval(this.input, runtimeOptions); } @@ -161,6 +169,8 @@ class HandlebarsTestBench { this.runtimeOptions ); + this.beforeRenderFn(); + return renderAST(this.input, runtimeOptions); } diff --git a/packages/kbn-handlebars/src/spec/.upstream_git_hash b/packages/kbn-handlebars/src/spec/.upstream_git_hash new file mode 100644 index 0000000000000..5a6b183166d46 --- /dev/null +++ b/packages/kbn-handlebars/src/spec/.upstream_git_hash @@ -0,0 +1 @@ +c65c6cce3f626e4896a9d59250f0908be695adae \ No newline at end of file diff --git a/packages/kbn-handlebars/src/upstream/index.basic.test.ts b/packages/kbn-handlebars/src/spec/index.basic.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.basic.test.ts rename to packages/kbn-handlebars/src/spec/index.basic.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.blocks.test.ts b/packages/kbn-handlebars/src/spec/index.blocks.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.blocks.test.ts rename to packages/kbn-handlebars/src/spec/index.blocks.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.builtins.test.ts b/packages/kbn-handlebars/src/spec/index.builtins.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.builtins.test.ts rename to packages/kbn-handlebars/src/spec/index.builtins.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.compiler.test.ts b/packages/kbn-handlebars/src/spec/index.compiler.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.compiler.test.ts rename to packages/kbn-handlebars/src/spec/index.compiler.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.data.test.ts b/packages/kbn-handlebars/src/spec/index.data.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.data.test.ts rename to packages/kbn-handlebars/src/spec/index.data.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.helpers.test.ts b/packages/kbn-handlebars/src/spec/index.helpers.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.helpers.test.ts rename to packages/kbn-handlebars/src/spec/index.helpers.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.regressions.test.ts b/packages/kbn-handlebars/src/spec/index.regressions.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.regressions.test.ts rename to packages/kbn-handlebars/src/spec/index.regressions.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.security.test.ts b/packages/kbn-handlebars/src/spec/index.security.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.security.test.ts rename to packages/kbn-handlebars/src/spec/index.security.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.strict.test.ts b/packages/kbn-handlebars/src/spec/index.strict.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.strict.test.ts rename to packages/kbn-handlebars/src/spec/index.strict.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.subexpressions.test.ts b/packages/kbn-handlebars/src/spec/index.subexpressions.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.subexpressions.test.ts rename to packages/kbn-handlebars/src/spec/index.subexpressions.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.utils.test.ts b/packages/kbn-handlebars/src/spec/index.utils.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.utils.test.ts rename to packages/kbn-handlebars/src/spec/index.utils.test.ts diff --git a/packages/kbn-handlebars/src/upstream/index.whitespace-control.test.ts b/packages/kbn-handlebars/src/spec/index.whitespace_control.test.ts similarity index 100% rename from packages/kbn-handlebars/src/upstream/index.whitespace-control.test.ts rename to packages/kbn-handlebars/src/spec/index.whitespace_control.test.ts diff --git a/packages/shared-ux/code_editor/impl/kibana.jsonc b/packages/shared-ux/code_editor/impl/kibana.jsonc index d00f7a078b55e..d66e88d40710e 100644 --- a/packages/shared-ux/code_editor/impl/kibana.jsonc +++ b/packages/shared-ux/code_editor/impl/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/code-editor", - "owner": "@elastic/shared-ux" + "owner": "@elastic/appex-sharedux" } diff --git a/packages/shared-ux/code_editor/mocks/kibana.jsonc b/packages/shared-ux/code_editor/mocks/kibana.jsonc index d8d52615dc5be..dd258cfd76853 100644 --- a/packages/shared-ux/code_editor/mocks/kibana.jsonc +++ b/packages/shared-ux/code_editor/mocks/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/code-editor-mocks", - "owner": "@elastic/shared-ux" + "owner": "@elastic/appex-sharedux" } diff --git a/packages/shared-ux/code_editor/types/kibana.jsonc b/packages/shared-ux/code_editor/types/kibana.jsonc index c4fa126fd5da1..a4d4fb37dba34 100644 --- a/packages/shared-ux/code_editor/types/kibana.jsonc +++ b/packages/shared-ux/code_editor/types/kibana.jsonc @@ -1,5 +1,5 @@ { "type": "shared-common", "id": "@kbn/code-editor-types", - "owner": "@elastic/shared-ux" + "owner": "@elastic/appex-sharedux" } diff --git a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.test.tsx b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.test.tsx index 4bd3fca6cb902..1f657f642fc47 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.test.tsx +++ b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.test.tsx @@ -27,6 +27,7 @@ describe('AnalyticsNoDataPageComponent', () => { ); @@ -50,6 +51,7 @@ describe('AnalyticsNoDataPageComponent', () => { onDataViewCreated={onDataViewCreated} kibanaGuideDocLink={'http://www.test.com'} allowAdHocDataView={true} + showPlainSpinner={false} /> ); diff --git a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.tsx b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.tsx index fe607b70120df..d67cb082f5539 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.tsx +++ b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.component.tsx @@ -19,6 +19,8 @@ export interface Props { onDataViewCreated: (dataView: unknown) => void; /** if set to true allows creation of an ad-hoc dataview from data view editor */ allowAdHocDataView?: boolean; + /** if the kibana instance is customly branded */ + showPlainSpinner: boolean; } const solution = i18n.translate('sharedUXPackages.noDataConfig.analytics', { @@ -47,6 +49,7 @@ export const AnalyticsNoDataPage = ({ kibanaGuideDocLink, onDataViewCreated, allowAdHocDataView, + showPlainSpinner, }: Props) => { const noDataConfig = { solution, @@ -61,5 +64,10 @@ export const AnalyticsNoDataPage = ({ }, docsLink: kibanaGuideDocLink, }; - return ; + + return ( + + ); }; diff --git a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.test.tsx b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.test.tsx index 996b9d062becf..c73f61e6c0e82 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.test.tsx +++ b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.test.tsx @@ -9,7 +9,10 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { getAnalyticsNoDataPageServicesMock } from '@kbn/shared-ux-page-analytics-no-data-mocks'; +import { + getAnalyticsNoDataPageServicesMock, + getAnalyticsNoDataPageServicesMockWithCustomBranding, +} from '@kbn/shared-ux-page-analytics-no-data-mocks'; import { AnalyticsNoDataPageProvider } from './services'; import { AnalyticsNoDataPage as Component } from './analytics_no_data_page.component'; @@ -19,6 +22,7 @@ describe('AnalyticsNoDataPage', () => { const onDataViewCreated = jest.fn(); const services = getAnalyticsNoDataPageServicesMock(); + const servicesWithCustomBranding = getAnalyticsNoDataPageServicesMockWithCustomBranding(); afterAll(() => { jest.resetAllMocks(); @@ -38,4 +42,15 @@ describe('AnalyticsNoDataPage', () => { expect(component.find(Component).props().onDataViewCreated).toBe(onDataViewCreated); expect(component.find(Component).props().allowAdHocDataView).toBe(true); }); + + it('passes correct boolean value to showPlainSpinner', () => { + const component = mountWithIntl( + + + + ); + + expect(component.find(Component).length).toBe(1); + expect(component.find(Component).props().showPlainSpinner).toBe(true); + }); }); diff --git a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.tsx b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.tsx index df1fc2486c1b3..9b600c374dd02 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.tsx +++ b/packages/shared-ux/page/analytics_no_data/impl/src/analytics_no_data_page.tsx @@ -8,6 +8,7 @@ import React from 'react'; import type { AnalyticsNoDataPageProps } from '@kbn/shared-ux-page-analytics-no-data-types'; +import useObservable from 'react-use/lib/useObservable'; import { useServices } from './services'; import { AnalyticsNoDataPage as Component } from './analytics_no_data_page.component'; @@ -20,7 +21,9 @@ export const AnalyticsNoDataPage = ({ allowAdHocDataView, }: AnalyticsNoDataPageProps) => { const services = useServices(); - const { kibanaGuideDocLink } = services; + const { kibanaGuideDocLink, customBranding } = services; + const { hasCustomBranding$ } = customBranding; + const showPlainSpinner = useObservable(hasCustomBranding$) ?? false; return ( ); diff --git a/packages/shared-ux/page/analytics_no_data/impl/src/services.tsx b/packages/shared-ux/page/analytics_no_data/impl/src/services.tsx index bd486be8f8976..991893aeca501 100644 --- a/packages/shared-ux/page/analytics_no_data/impl/src/services.tsx +++ b/packages/shared-ux/page/analytics_no_data/impl/src/services.tsx @@ -27,10 +27,10 @@ export const AnalyticsNoDataPageProvider: FC = ({ children, ...services }) => { - const { kibanaGuideDocLink } = services; + const { kibanaGuideDocLink, customBranding } = services; return ( - + {children} ); @@ -45,8 +45,10 @@ export const AnalyticsNoDataPageKibanaProvider: FC { const value: Services = { kibanaGuideDocLink: dependencies.coreStart.docLinks.links.kibana.guide, + customBranding: { + hasCustomBranding$: dependencies.coreStart.customBranding.hasCustomBranding$, + }, }; - return ( {children} diff --git a/packages/shared-ux/page/analytics_no_data/mocks/index.ts b/packages/shared-ux/page/analytics_no_data/mocks/index.ts index cc73dc378452b..1f1ac86e9d247 100644 --- a/packages/shared-ux/page/analytics_no_data/mocks/index.ts +++ b/packages/shared-ux/page/analytics_no_data/mocks/index.ts @@ -7,6 +7,7 @@ */ export { getServicesMock as getAnalyticsNoDataPageServicesMock } from './src/jest'; +export { getServicesMockCustomBranding as getAnalyticsNoDataPageServicesMockWithCustomBranding } from './src/jest'; export { StorybookMock as AnalyticsNoDataPageStorybookMock } from './src/storybook'; export type { Params as AnalyticsNoDataPageStorybookParams } from './src/storybook'; diff --git a/packages/shared-ux/page/analytics_no_data/mocks/src/jest.ts b/packages/shared-ux/page/analytics_no_data/mocks/src/jest.ts index 29a79c40054aa..98885d55ba47d 100644 --- a/packages/shared-ux/page/analytics_no_data/mocks/src/jest.ts +++ b/packages/shared-ux/page/analytics_no_data/mocks/src/jest.ts @@ -8,11 +8,24 @@ import type { AnalyticsNoDataPageServices } from '@kbn/shared-ux-page-analytics-no-data-types'; import { getKibanaNoDataPageServicesMock } from '@kbn/shared-ux-page-kibana-no-data-mocks'; +import { of } from 'rxjs'; export const getServicesMock = () => { const services: AnalyticsNoDataPageServices = { ...getKibanaNoDataPageServicesMock(), kibanaGuideDocLink: 'Kibana guide', + customBranding: { hasCustomBranding$: of(false) }, + }; + + return services; +}; + +export const getServicesMockCustomBranding = () => { + const services: AnalyticsNoDataPageServices = { + ...getKibanaNoDataPageServicesMock(), + // this mock will have custom branding set to true + customBranding: { hasCustomBranding$: of(true) }, + kibanaGuideDocLink: 'Kibana guide', }; return services; diff --git a/packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts b/packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts index c909795647c4e..86bf25dbde9e9 100644 --- a/packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts +++ b/packages/shared-ux/page/analytics_no_data/mocks/src/storybook.ts @@ -15,8 +15,9 @@ import type { AnalyticsNoDataPageServices, AnalyticsNoDataPageProps, } from '@kbn/shared-ux-page-analytics-no-data-types'; +import { of } from 'rxjs'; -type ServiceArguments = Pick; +type ServiceArguments = Pick; export type Params = ArgumentParams<{}, ServiceArguments> & KibanaNoDataPageStorybookParams; @@ -34,6 +35,12 @@ export class StorybookMock extends AbstractStorybookMock< control: 'text', defaultValue: 'Kibana guide', }, + customBranding: { + hasCustomBranding$: { + control: 'boolean', + defaultValue: false, + }, + }, }; dependencies = [kibanaNoDataMock]; @@ -41,6 +48,9 @@ export class StorybookMock extends AbstractStorybookMock< getServices(params: Params): AnalyticsNoDataPageServices { return { kibanaGuideDocLink: 'Kibana guide', + customBranding: { + hasCustomBranding$: of(false), + }, ...kibanaNoDataMock.getServices(params), }; } diff --git a/packages/shared-ux/page/analytics_no_data/types/index.d.ts b/packages/shared-ux/page/analytics_no_data/types/index.d.ts index d4021360bea23..4e54315f071dd 100644 --- a/packages/shared-ux/page/analytics_no_data/types/index.d.ts +++ b/packages/shared-ux/page/analytics_no_data/types/index.d.ts @@ -9,12 +9,14 @@ import { KibanaNoDataPageServices, KibanaNoDataPageKibanaDependencies, } from '@kbn/shared-ux-page-kibana-no-data-types'; +import { Observable } from 'rxjs'; /** * A list of services that are consumed by this component. */ export interface Services { kibanaGuideDocLink: string; + customBranding: { hasCustomBranding$: Observable }; } /** @@ -31,6 +33,9 @@ export interface KibanaDependencies { }; }; }; + customBranding: { + hasCustomBranding$: Observable; + }; }; } diff --git a/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.test.tsx b/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.test.tsx index c15a5c061dd1b..a3484719a49ed 100644 --- a/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.test.tsx +++ b/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { EuiLoadingElastic } from '@elastic/eui'; +import { EuiLoadingElastic, EuiLoadingSpinner } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { NoDataViewsPrompt } from '@kbn/shared-ux-prompt-no-data-views'; import { NoDataConfigPage } from '@kbn/shared-ux-page-no-data-config'; @@ -46,7 +46,7 @@ describe('Kibana No Data Page', () => { const services = getKibanaNoDataPageServicesMock(config); const component = mountWithIntl( - + ); @@ -61,7 +61,11 @@ describe('Kibana No Data Page', () => { const services = getKibanaNoDataPageServicesMock({ ...config, hasESData: true }); const component = mountWithIntl( - + ); @@ -86,7 +90,11 @@ describe('Kibana No Data Page', () => { const component = mountWithIntl( - + ); @@ -96,4 +104,15 @@ describe('Kibana No Data Page', () => { expect(component.find(NoDataViewsPrompt).length).toBe(0); expect(component.find(NoDataConfigPage).length).toBe(0); }); + + test('shows EuiLoadingSpinner vs EuiLoadingElastic for custom branding', () => { + const services = getKibanaNoDataPageServicesMock(config); + const component = mountWithIntl( + + + + ); + expect(component.find(EuiLoadingSpinner).length).toBe(1); + expect(component.find(EuiLoadingElastic).length).toBe(0); + }); }); diff --git a/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.tsx b/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.tsx index c3fbccd3a60fb..2773184b087bb 100644 --- a/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.tsx +++ b/packages/shared-ux/page/kibana_no_data/impl/src/kibana_no_data_page.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import React, { useEffect, useState } from 'react'; -import { EuiLoadingElastic } from '@elastic/eui'; +import { EuiLoadingElastic, EuiLoadingSpinner } from '@elastic/eui'; import { NoDataConfigPage } from '@kbn/shared-ux-page-no-data-config'; import { NoDataViewsPrompt } from '@kbn/shared-ux-prompt-no-data-views'; import { KibanaNoDataPageProps } from '@kbn/shared-ux-page-kibana-no-data-types'; @@ -20,6 +20,7 @@ export const KibanaNoDataPage = ({ onDataViewCreated, noDataConfig, allowAdHocDataView, + showPlainSpinner, }: KibanaNoDataPageProps) => { // These hooks are temporary, until this component is moved to a package. const services = useServices(); @@ -43,7 +44,11 @@ export const KibanaNoDataPage = ({ }, [hasESData, hasUserDataView]); if (isLoading) { - return ; + return showPlainSpinner ? ( + + ) : ( + + ); } if (!hasUserDataViews && dataExists) { diff --git a/packages/shared-ux/page/kibana_no_data/mocks/src/jest.ts b/packages/shared-ux/page/kibana_no_data/mocks/src/jest.ts index 5f2f6b309e56c..dc46f22286646 100644 --- a/packages/shared-ux/page/kibana_no_data/mocks/src/jest.ts +++ b/packages/shared-ux/page/kibana_no_data/mocks/src/jest.ts @@ -13,11 +13,13 @@ import { getNoDataViewsPromptServicesMock } from '@kbn/shared-ux-prompt-no-data- interface Params { hasESData: boolean; hasUserDataView: boolean; + showPlainSpinner: boolean; } const defaultParams = { hasESData: true, hasUserDataView: true, + showPlainSpinner: false, }; /** diff --git a/packages/shared-ux/page/kibana_no_data/mocks/src/storybook.ts b/packages/shared-ux/page/kibana_no_data/mocks/src/storybook.ts index 1f4a7453e59b6..10cc9a0f40961 100644 --- a/packages/shared-ux/page/kibana_no_data/mocks/src/storybook.ts +++ b/packages/shared-ux/page/kibana_no_data/mocks/src/storybook.ts @@ -79,7 +79,11 @@ export class StorybookMock extends AbstractStorybookMock< docsLink: 'http://docs.elastic.dev', }; - return { noDataConfig, onDataViewCreated: action('onDataViewCreated') }; + return { + showPlainSpinner: false, + noDataConfig, + onDataViewCreated: action('onDataViewCreated'), + }; } getServices(params: Params): KibanaNoDataPageServices { diff --git a/packages/shared-ux/page/kibana_no_data/types/index.d.ts b/packages/shared-ux/page/kibana_no_data/types/index.d.ts index 1cce51f372021..ff9b4d845f597 100644 --- a/packages/shared-ux/page/kibana_no_data/types/index.d.ts +++ b/packages/shared-ux/page/kibana_no_data/types/index.d.ts @@ -57,4 +57,6 @@ export interface KibanaNoDataPageProps { noDataConfig: NoDataPageProps; /** if set to true allows creation of an ad-hoc dataview from data view editor */ allowAdHocDataView?: boolean; + /** Set to true if the kibana is customly branded */ + showPlainSpinner: boolean; } diff --git a/packages/shared-ux/prompt/not_found/src/not_found_prompt.tsx b/packages/shared-ux/prompt/not_found/src/not_found_prompt.tsx index bd607e1beae07..116deb7098c39 100644 --- a/packages/shared-ux/prompt/not_found/src/not_found_prompt.tsx +++ b/packages/shared-ux/prompt/not_found/src/not_found_prompt.tsx @@ -32,12 +32,14 @@ const NOT_FOUND_GO_BACK = i18n.translate('sharedUXPackages.prompt.errors.notFoun interface NotFoundProps { /** Array of buttons, links and other actions to show at the bottom of the `EuiEmptyPrompt`. Defaults to a "Back" button. */ actions?: EuiEmptyPromptProps['actions']; + title?: EuiEmptyPromptProps['title'] | string; + body?: EuiEmptyPromptProps['body']; } /** * Predefined `EuiEmptyPrompt` for 404 pages. */ -export const NotFoundPrompt = ({ actions }: NotFoundProps) => { +export const NotFoundPrompt = ({ actions, title, body }: NotFoundProps) => { const { colorMode } = useEuiTheme(); const [imageSrc, setImageSrc] = useState(); const goBack = useCallback(() => history.back(), []); @@ -71,8 +73,8 @@ export const NotFoundPrompt = ({ actions }: NotFoundProps) => { color="subdued" titleSize="m" icon={icon} - title={

      {NOT_FOUND_TITLE}

      } - body={NOT_FOUND_BODY} + title={typeof title === 'string' || !title ?

      {title ?? NOT_FOUND_TITLE}

      : title} + body={body ?? NOT_FOUND_BODY} actions={actions ?? DEFAULT_ACTIONS} /> ); diff --git a/src/core/public/styles/rendering/_base.scss b/src/core/public/styles/rendering/_base.scss index b64595e69a791..9d4296ca3b4ef 100644 --- a/src/core/public/styles/rendering/_base.scss +++ b/src/core/public/styles/rendering/_base.scss @@ -62,6 +62,8 @@ @include kbnAffordForHeader($kbnHeaderOffset); &.kbnBody--hasHeaderBanner { + padding-top: $kbnHeaderBannerHeight; + @include kbnAffordForHeader($kbnHeaderOffsetWithBanner); // Prevents banners from covering full screen data grids diff --git a/src/plugins/dashboard/public/dashboard_app/no_data/dashboard_app_no_data.tsx b/src/plugins/dashboard/public/dashboard_app/no_data/dashboard_app_no_data.tsx index 603a36bb3d59b..fb04a3187c72c 100644 --- a/src/plugins/dashboard/public/dashboard_app/no_data/dashboard_app_no_data.tsx +++ b/src/plugins/dashboard/public/dashboard_app/no_data/dashboard_app_no_data.tsx @@ -25,6 +25,7 @@ export const DashboardAppNoDataPage = ({ dataViewEditor, http: { basePath }, documentationLinks: { indexPatternsDocLink, kibanaGuideDocLink }, + customBranding, } = pluginServices.getServices(); const analyticsServices = { @@ -37,6 +38,9 @@ export const DashboardAppNoDataPage = ({ }, application, http: { basePath }, + customBranding: { + hasCustomBranding$: customBranding.hasCustomBranding$, + }, }, dataViews, dataViewEditor, diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index fa457c425440e..1d562877a5dea 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -49,6 +49,7 @@ import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plu import type { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; import type { SavedObjectTaggingOssPluginStart } from '@kbn/saved-objects-tagging-oss-plugin/public'; +import { CustomBrandingStart } from '@kbn/core-custom-branding-browser'; import { DashboardContainerFactoryDefinition } from './dashboard_container/embeddable/dashboard_container_factory'; import { type DashboardAppLocator, @@ -97,6 +98,7 @@ export interface DashboardStartDependencies { urlForwarding: UrlForwardingStart; usageCollection?: UsageCollectionStart; visualizations: VisualizationsStart; + customBranding: CustomBrandingStart; } export interface DashboardSetup { diff --git a/src/plugins/dashboard/public/services/custom_branding/custom_branding.stub.ts b/src/plugins/dashboard/public/services/custom_branding/custom_branding.stub.ts new file mode 100644 index 0000000000000..5496c29b760f9 --- /dev/null +++ b/src/plugins/dashboard/public/services/custom_branding/custom_branding.stub.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { PluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import { DashboardCustomBrandingService } from './types'; + +type CustomBrandingServiceFactory = PluginServiceFactory; + +export const customBrandingServiceFactory: CustomBrandingServiceFactory = () => { + const pluginMock = coreMock.createStart(); + return { + hasCustomBranding$: pluginMock.customBranding.hasCustomBranding$, + }; +}; diff --git a/src/plugins/dashboard/public/services/custom_branding/custom_branding_service.ts b/src/plugins/dashboard/public/services/custom_branding/custom_branding_service.ts new file mode 100644 index 0000000000000..659a669a5bda1 --- /dev/null +++ b/src/plugins/dashboard/public/services/custom_branding/custom_branding_service.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. + */ + +import type { KibanaPluginServiceFactory } from '@kbn/presentation-util-plugin/public'; +import { DashboardStartDependencies } from '../../plugin'; +import { DashboardCustomBrandingService } from './types'; + +export type CustomBrandingServiceFactory = KibanaPluginServiceFactory< + DashboardCustomBrandingService, + DashboardStartDependencies +>; + +export const customBrandingServiceFactory: CustomBrandingServiceFactory = ({ coreStart }) => { + const { customBranding } = coreStart; + return { + hasCustomBranding$: customBranding.hasCustomBranding$, + }; +}; diff --git a/src/plugins/dashboard/public/services/custom_branding/types.ts b/src/plugins/dashboard/public/services/custom_branding/types.ts new file mode 100644 index 0000000000000..7e7e88bb15a7a --- /dev/null +++ b/src/plugins/dashboard/public/services/custom_branding/types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CustomBrandingStart } from '@kbn/core-custom-branding-browser'; + +export interface DashboardCustomBrandingService { + hasCustomBranding$: CustomBrandingStart['hasCustomBranding$']; +} diff --git a/src/plugins/dashboard/public/services/plugin_services.stub.ts b/src/plugins/dashboard/public/services/plugin_services.stub.ts index b8c39909dd61a..eabe85288687a 100644 --- a/src/plugins/dashboard/public/services/plugin_services.stub.ts +++ b/src/plugins/dashboard/public/services/plugin_services.stub.ts @@ -38,6 +38,7 @@ import { spacesServiceFactory } from './spaces/spaces.stub'; import { urlForwardingServiceFactory } from './url_forwarding/url_fowarding.stub'; import { visualizationsServiceFactory } from './visualizations/visualizations.stub'; import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object.stub'; +import { customBrandingServiceFactory } from './custom_branding/custom_branding.stub'; export const providers: PluginServiceProviders = { dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory), @@ -64,6 +65,7 @@ export const providers: PluginServiceProviders = { urlForwarding: new PluginServiceProvider(urlForwardingServiceFactory), usageCollection: new PluginServiceProvider(usageCollectionServiceFactory), visualizations: new PluginServiceProvider(visualizationsServiceFactory), + customBranding: new PluginServiceProvider(customBrandingServiceFactory), }; export const registry = new PluginServiceRegistry(providers); diff --git a/src/plugins/dashboard/public/services/plugin_services.ts b/src/plugins/dashboard/public/services/plugin_services.ts index b4ee1b566a8ac..4382506a37948 100644 --- a/src/plugins/dashboard/public/services/plugin_services.ts +++ b/src/plugins/dashboard/public/services/plugin_services.ts @@ -39,6 +39,7 @@ import { visualizationsServiceFactory } from './visualizations/visualizations_se import { usageCollectionServiceFactory } from './usage_collection/usage_collection_service'; import { analyticsServiceFactory } from './analytics/analytics_service'; import { dashboardSavedObjectServiceFactory } from './dashboard_saved_object/dashboard_saved_object_service'; +import { customBrandingServiceFactory } from './custom_branding/custom_branding_service'; const providers: PluginServiceProviders = { dashboardSavedObject: new PluginServiceProvider(dashboardSavedObjectServiceFactory, [ @@ -78,6 +79,7 @@ const providers: PluginServiceProviders(); diff --git a/src/plugins/dashboard/public/services/types.ts b/src/plugins/dashboard/public/services/types.ts index 5d14b59e8a125..fc7e0acf1b5c4 100644 --- a/src/plugins/dashboard/public/services/types.ts +++ b/src/plugins/dashboard/public/services/types.ts @@ -14,6 +14,7 @@ import { DashboardAnalyticsService } from './analytics/types'; import { DashboardApplicationService } from './application/types'; import { DashboardChromeService } from './chrome/types'; import { DashboardCoreContextService } from './core_context/types'; +import { DashboardCustomBrandingService } from './custom_branding/types'; import { DashboardCapabilitiesService } from './dashboard_capabilities/types'; import { DashboardSavedObjectService } from './dashboard_saved_object/types'; import { DashboardSessionStorageServiceType } from './dashboard_session_storage/types'; @@ -64,4 +65,5 @@ export interface DashboardServices { urlForwarding: DashboardUrlForwardingService; usageCollection: DashboardUsageCollectionService; // TODO: make this optional in follow up visualizations: DashboardVisualizationsService; + customBranding: DashboardCustomBrandingService; } diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index c79c0bc281042..8ab580ccdfe72 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -51,6 +51,7 @@ "@kbn/core-saved-objects-common", "@kbn/task-manager-plugin", "@kbn/core-execution-context-common", + "@kbn/core-custom-branding-browser", ], "exclude": [ "target/**/*", diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 2ce045f1b6d66..f06a15edf044a 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -114,7 +114,7 @@ describe('SearchSource', () => { }); describe('#getActiveIndexFilter()', () => { - test('pase _index from query', () => { + test('pass _index from query', () => { searchSource.setField('query', { language: 'kuery', query: `_INDEX : fakebeat and _index : "mybeat-*"`, @@ -122,7 +122,7 @@ describe('SearchSource', () => { expect(searchSource.getActiveIndexFilter()).toMatchObject(['mybeat-*']); }); - test('pase _index from filter', () => { + test('pass _index from filter', () => { const filter = [ { query: { match_phrase: { _index: 'auditbeat-*' } }, @@ -163,7 +163,7 @@ describe('SearchSource', () => { expect(searchSource.getActiveIndexFilter()).toMatchObject(['auditbeat-*']); }); - test('pase _index from query and filter with negate equals to true', () => { + test('pass _index from query and filter with negate equals to true', () => { const filter = [ { query: { @@ -189,7 +189,7 @@ describe('SearchSource', () => { expect(searchSource.getActiveIndexFilter()).toMatchObject([]); }); - test('pase _index from query and filter with negate equals to true and disabled equals to true', () => { + test('pass _index from query and filter with negate equals to true and disabled equals to true', () => { const filter = [ { query: { diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 14b2e364878ef..0d79d469f40c8 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -59,7 +59,17 @@ */ import { setWith } from '@kbn/safer-lodash-set'; -import { difference, isEqual, isFunction, isObject, keyBy, pick, uniqueId, uniqWith } from 'lodash'; +import { + difference, + isEqual, + isFunction, + isObject, + keyBy, + pick, + uniqueId, + uniqWith, + concat, +} from 'lodash'; import { catchError, finalize, @@ -72,7 +82,7 @@ import { } from 'rxjs/operators'; import { defer, EMPTY, from, lastValueFrom, Observable } from 'rxjs'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { buildEsQuery, Filter, isOfQueryType } from '@kbn/es-query'; +import { buildEsQuery, Filter, isOfQueryType, isPhraseFilter } from '@kbn/es-query'; import { fieldWildcardFilter } from '@kbn/kibana-utils-plugin/common'; import { getHighlightRequest } from '@kbn/field-formats-plugin/common'; import type { DataView } from '@kbn/data-views-plugin/common'; @@ -81,7 +91,6 @@ import { buildExpression, buildExpressionFunction, } from '@kbn/expressions-plugin/common'; -import _ from 'lodash'; import { normalizeSortRequest } from './normalize_sort_request'; import { AggConfigSerialized, DataViewField, SerializedSearchSourceFields } from '../..'; @@ -251,7 +260,6 @@ export class SearchSource { getActiveIndexFilter() { const { filter: originalFilters, query } = this.getFields(); - let filters: Filter[] = []; if (originalFilters) { filters = this.getFilters(originalFilters); @@ -270,16 +278,16 @@ export class SearchSource { return acc.concat(this.parseActiveIndexPatternFromQueryString(currStr)); }, []) ?? []; - const activeIndexPattern: string[] = filters?.reduce((acc, f) => { - if (f.meta.key === '_index' && f.meta.disabled === false) { - if (f.meta.negate === false) { - return _.concat(acc, f.meta.params.query ?? f.meta.params); - } else { - if (Array.isArray(f.meta.params)) { - return _.difference(acc, f.meta.params); + const activeIndexPattern = filters?.reduce((acc, f) => { + if (isPhraseFilter(f)) { + if (f.meta.key === '_index' && f.meta.disabled === false) { + if (f.meta.negate === false) { + return concat(acc, f.meta.params?.query ?? f.meta.params); } else { - return _.difference(acc, [f.meta.params.query]); + return difference(acc, [f.meta.params?.query]); } + } else { + return acc; } } else { return acc; diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts index 6058a125c9a54..0471b1e4bee7f 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrase.ts @@ -25,7 +25,7 @@ export function getPhraseDisplayValue( formatter?: FieldFormat, fieldType?: string ): string { - const value = filter.meta.value ?? filter.meta.params.query; + const value = filter.meta.value ?? filter.meta.params?.query; const updatedValue = fieldType === 'number' && !value ? 0 : value; if (formatter?.convert) { return formatter.convert(updatedValue); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts index 48ca3852e715d..3a94972a5e69b 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_phrases.ts @@ -11,7 +11,7 @@ import { FieldFormat } from '@kbn/field-formats-plugin/common'; export function getPhrasesDisplayValue(filter: PhrasesFilter, formatter?: FieldFormat) { return filter.meta.params - .map((v: string) => { + .map((v) => { return formatter?.convert(v) ?? v; }) .join(', '); diff --git a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts index 04eb67e792163..f3fcb3ded54a6 100644 --- a/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts +++ b/src/plugins/data/public/query/filter_manager/lib/mappers/map_range.ts @@ -21,8 +21,8 @@ export function getRangeDisplayValue( { meta: { params } }: RangeFilter | ScriptedRangeFilter, formatter?: FieldFormat ) { - const left = params.gte ?? params.gt ?? -Infinity; - const right = params.lte ?? params.lt ?? Infinity; + const left = params?.gte ?? params?.gt ?? -Infinity; + const right = params?.lte ?? params?.lt ?? Infinity; if (!formatter) return `${left} to ${right}`; const convert = formatter.getConverterFor('text'); return `${convert(left)} to ${convert(right)}`; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index ab8c5da67d04a..2eec55daf742d 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -19,7 +19,6 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; -import { isOfQueryType } from '@kbn/es-query'; import classNames from 'classnames'; import { generateFilters } from '@kbn/data-plugin/public'; import { DataView, DataViewField, DataViewType } from '@kbn/data-views-plugin/public'; @@ -42,7 +41,6 @@ import { DataMainMsg, RecordRawType } from '../../services/discover_data_state_c import { useColumns } from '../../../../hooks/use_data_grid_columns'; import { FetchStatus } from '../../../types'; import { useDataState } from '../../hooks/use_data_state'; -import { hasActiveFilter } from './utils'; import { getRawRecordType } from '../../utils/get_raw_record_type'; import { SavedSearchURLConflictCallout } from '../../../../components/saved_search_url_conflict_callout/saved_search_url_conflict_callout'; import { DiscoverHistogramLayout } from './discover_histogram_layout'; @@ -84,10 +82,9 @@ export function DiscoverLayout({ inspector, } = useDiscoverServices(); const { main$ } = stateContainer.dataState.data$; - const [query, savedQuery, filters, columns, sort] = useAppStateSelector((state) => [ + const [query, savedQuery, columns, sort] = useAppStateSelector((state) => [ state.query, state.savedQuery, - state.filters, state.columns, state.sort, ]); @@ -208,13 +205,16 @@ export function DiscoverLayout({ const mainDisplay = useMemo(() => { if (resultState === 'none') { + const globalQueryState = data.query.getState(); + return ( ); @@ -257,7 +257,6 @@ export function DiscoverLayout({ dataState.error, dataView, expandedDoc, - filters, inspectorAdapters, isPlainRecord, isTimeBased, @@ -265,7 +264,6 @@ export function DiscoverLayout({ onAddFilter, onDisableFilters, onFieldEdited, - query, resetSavedSearch, resultState, savedSearch, diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx index 6ead00f8cce06..98b5d6f46fcc8 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx @@ -7,47 +7,83 @@ */ import React from 'react'; +import { ReactWrapper } from 'enzyme'; +import * as RxApi from 'rxjs'; +import { act } from 'react-dom/test-utils'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; - -import { DiscoverNoResults, DiscoverNoResultsProps } from './no_results'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { + stubDataView, + stubDataViewWithoutTimeField, +} from '@kbn/data-views-plugin/common/data_view.stub'; +import { type Filter } from '@kbn/es-query'; +import { DiscoverNoResults, DiscoverNoResultsProps } from './no_results'; +import { createDiscoverServicesMock } from '../../../../__mocks__/services'; -beforeEach(() => { - jest.clearAllMocks(); -}); - -function mountAndFindSubjects(props: Omit) { - const services = { - docLinks: { - links: { - query: { - luceneQuerySyntax: 'documentation-link', - }, +jest.spyOn(RxApi, 'lastValueFrom').mockImplementation(async () => ({ + rawResponse: { + aggregations: { + earliest_timestamp: { + value_as_string: '2020-09-01T08:30:00.000Z', + }, + latest_timestamp: { + value_as_string: '2022-09-01T08:30:00.000Z', }, }, - }; - const component = mountWithIntl( - - {}} {...props} /> - - ); + }, +})); + +async function mountAndFindSubjects( + props: Omit +) { + const services = createDiscoverServicesMock(); + + let component: ReactWrapper; + + await act(async () => { + component = await mountWithIntl( + + {}} + {...props} + /> + + ); + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + await act(async () => { + await component!.update(); + }); + return { - mainMsg: findTestSubject(component, 'discoverNoResults').exists(), - errorMsg: findTestSubject(component, 'discoverNoResultsError').exists(), - adjustTimeRange: findTestSubject(component, 'discoverNoResultsTimefilter').exists(), - adjustSearch: findTestSubject(component, 'discoverNoResultsAdjustSearch').exists(), - adjustFilters: findTestSubject(component, 'discoverNoResultsAdjustFilters').exists(), - checkIndices: findTestSubject(component, 'discoverNoResultsCheckIndices').exists(), - disableFiltersButton: findTestSubject(component, 'discoverNoResultsDisableFilters').exists(), + mainMsg: findTestSubject(component!, 'discoverNoResults').exists(), + errorMsg: findTestSubject(component!, 'discoverNoResultsError').exists(), + adjustTimeRange: findTestSubject(component!, 'discoverNoResultsTimefilter').exists(), + adjustSearch: findTestSubject(component!, 'discoverNoResultsAdjustSearch').exists(), + adjustFilters: findTestSubject(component!, 'discoverNoResultsAdjustFilters').exists(), + checkIndices: findTestSubject(component!, 'discoverNoResultsCheckIndices').exists(), + disableFiltersButton: findTestSubject(component!, 'discoverNoResultsDisableFilters').exists(), + viewMatchesButton: findTestSubject(component!, 'discoverNoResultsViewAllMatches').exists(), }; } describe('DiscoverNoResults', () => { + beforeEach(() => { + (RxApi.lastValueFrom as jest.Mock).mockClear(); + }); + describe('props', () => { describe('no props', () => { - test('renders default feedback', () => { - const result = mountAndFindSubjects({}); + test('renders default feedback', async () => { + const result = await mountAndFindSubjects({ + dataView: stubDataViewWithoutTimeField, + query: undefined, + filters: undefined, + }); expect(result).toMatchInlineSnapshot(` Object { "adjustFilters": false, @@ -57,14 +93,17 @@ describe('DiscoverNoResults', () => { "disableFiltersButton": false, "errorMsg": false, "mainMsg": true, + "viewMatchesButton": false, } `); }); }); describe('timeFieldName', () => { - test('renders time range feedback', () => { - const result = mountAndFindSubjects({ - isTimeBased: true, + test('renders time range feedback', async () => { + const result = await mountAndFindSubjects({ + dataView: stubDataView, + query: { language: 'lucene', query: '' }, + filters: [], }); expect(result).toMatchInlineSnapshot(` Object { @@ -75,30 +114,42 @@ describe('DiscoverNoResults', () => { "disableFiltersButton": false, "errorMsg": false, "mainMsg": true, + "viewMatchesButton": true, } `); + expect(RxApi.lastValueFrom).toHaveBeenCalledTimes(1); }); }); describe('filter/query', () => { - test('shows "adjust search" message when having query', () => { - const result = mountAndFindSubjects({ hasQuery: true }); + test('shows "adjust search" message when having query', async () => { + const result = await mountAndFindSubjects({ + dataView: stubDataView, + query: { language: 'lucene', query: '*' }, + filters: undefined, + }); expect(result).toHaveProperty('adjustSearch', true); }); - test('shows "adjust filters" message when having filters', () => { - const result = mountAndFindSubjects({ hasFilters: true }); + test('shows "adjust filters" message when having filters', async () => { + const result = await mountAndFindSubjects({ + dataView: stubDataView, + query: { language: 'lucene', query: '' }, + filters: [{} as Filter], + }); expect(result).toHaveProperty('adjustFilters', true); expect(result).toHaveProperty('disableFiltersButton', true); }); }); describe('error message', () => { - test('renders error message', () => { + test('renders error message', async () => { const error = new Error('Fatal error'); - const result = mountAndFindSubjects({ - isTimeBased: true, + const result = await mountAndFindSubjects({ + dataView: stubDataView, error, + query: { language: 'lucene', query: '' }, + filters: [{} as Filter], }); expect(result).toMatchInlineSnapshot(` Object { @@ -109,6 +160,7 @@ describe('DiscoverNoResults', () => { "disableFiltersButton": false, "errorMsg": true, "mainMsg": false, + "viewMatchesButton": false, } `); }); diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results.tsx index 4e6f4425f56e0..c24423693a622 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results.tsx @@ -8,60 +8,41 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiButton, - EuiCallOut, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiTitle, -} from '@elastic/eui'; -import { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { NoResultsSuggestions } from './no_results_suggestions'; import './_no_results.scss'; -import { NoResultsIllustration } from './assets/no_results_illustration'; export interface DiscoverNoResultsProps { isTimeBased?: boolean; + query: Query | AggregateQuery | undefined; + filters: Filter[] | undefined; error?: Error; - data?: DataPublicPluginStart; - hasQuery?: boolean; - hasFilters?: boolean; + data: DataPublicPluginStart; + dataView: DataView; onDisableFilters: () => void; } export function DiscoverNoResults({ isTimeBased, + query, + filters, error, data, - hasFilters, - hasQuery, + dataView, onDisableFilters, }: DiscoverNoResultsProps) { const callOut = !error ? ( - - -

      - -

      -
      - - - - - - - - - + + ) : ( diff --git a/src/plugins/discover/public/application/main/components/no_results/assets/no_results_illustration.scss b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/assets/no_results_illustration.scss similarity index 100% rename from src/plugins/discover/public/application/main/components/no_results/assets/no_results_illustration.scss rename to src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/assets/no_results_illustration.scss diff --git a/src/plugins/discover/public/application/main/components/no_results/assets/no_results_illustration.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/assets/no_results_illustration.tsx similarity index 99% rename from src/plugins/discover/public/application/main/components/no_results/assets/no_results_illustration.tsx rename to src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/assets/no_results_illustration.tsx index b96fad88f1b44..10fc01537688a 100644 --- a/src/plugins/discover/public/application/main/components/no_results/assets/no_results_illustration.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/assets/no_results_illustration.tsx @@ -12,8 +12,8 @@ import React from 'react'; export const NoResultsIllustration = () => ( diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_default.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_default.tsx index b232b4138ea69..b90ca64c23e64 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_default.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_default.tsx @@ -8,17 +8,15 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiDescriptionList, EuiDescriptionListDescription } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; export function NoResultsSuggestionDefault() { return ( - - - - - + + + ); } diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_filters.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_filters.tsx index b153f6046b104..4112161aa5f29 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_filters.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_filters.tsx @@ -8,12 +8,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiDescriptionList, - EuiDescriptionListTitle, - EuiLink, - EuiDescriptionListDescription, -} from '@elastic/eui'; +import { EuiLink, EuiText } from '@elastic/eui'; export interface NoResultsSuggestionWhenFiltersProps { onDisableFilters: () => void; @@ -23,29 +18,21 @@ export function NoResultsSuggestionWhenFilters({ onDisableFilters, }: NoResultsSuggestionWhenFiltersProps) { return ( - - - - - - - - - ), - }} - /> - - + + + + + ), + }} + /> + ); } diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_query.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_query.tsx index 166b2a7f742cd..d6ecb53a8025f 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_query.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_query.tsx @@ -7,25 +7,199 @@ */ import React from 'react'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiDescriptionList, - EuiDescriptionListTitle, - EuiDescriptionListDescription, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLink } from '@elastic/eui'; +import { SyntaxExamples, SyntaxSuggestionsPopover } from './syntax_suggestions_popover'; +import { type DiscoverServices } from '../../../../../build_services'; +import { useDiscoverServices } from '../../../../../hooks/use_discover_services'; -export function NoResultsSuggestionWhenQuery() { - return ( - - - - - +const getExamples = ( + querySyntax: string | undefined, + docLinks: DiscoverServices['docLinks'] +): SyntaxExamples | null => { + if (!querySyntax) { + return null; + } + + if (querySyntax === 'lucene') { + return { + title: i18n.translate('discover.noResults.luceneExamples.title', { + defaultMessage: 'Lucene examples', + }), + items: [ + { + label: i18n.translate( + 'discover.noResults.luceneExamples.findRequestsThatContain200Text', + { + defaultMessage: 'Find requests that contain the number 200, in any field', + } + ), + example: '200', + }, + { + label: i18n.translate('discover.noResults.luceneExamples.find200InStatusFieldText', { + defaultMessage: 'Find 200 in the status field', + }), + example: 'status:200', + }, + { + label: i18n.translate('discover.noResults.luceneExamples.findAllStatusCodesText', { + defaultMessage: 'Find all status codes between 400-499', + }), + example: 'status:[400 TO 499]', + }, + { + label: i18n.translate('discover.noResults.luceneExamples.findStatusCodesWithPHPText', { + defaultMessage: 'Find status codes 400-499 with the extension php', + }), + example: 'status:[400 TO 499] AND extension:PHP', + }, + { + label: i18n.translate( + 'discover.noResults.luceneExamples.findStatusCodesWithPhpOrHtmlText', + { + defaultMessage: 'Find status codes 400-499 with the extension php or html', + } + ), + example: 'status:[400 TO 499] AND (extension:php OR extension:html)', + }, + ], + footer: ( + + + ), + }} /> - - - ); + ), + }; + } + + if (querySyntax === 'kuery') { + return { + title: i18n.translate('discover.noResults.kqlExamples.title', { + defaultMessage: 'KQL examples', + }), + items: [ + { + label: i18n.translate('discover.noResults.kqlExamples.filterForExistingFieldsText', { + defaultMessage: 'Filter for documents where a field exists', + }), + example: 'http.request.method: *', + }, + { + label: i18n.translate('discover.noResults.kqlExamples.filterForDocsThatMatchValueText', { + defaultMessage: 'Filter for documents that match a value', + }), + example: 'http.request.method: GET', + }, + { + label: i18n.translate('discover.noResults.kqlExamples.filterForDocsWithinRangeText', { + defaultMessage: 'Filter for documents within a range', + }), + example: 'http.response.bytes > 10000 and http.response.bytes <= 20000', + }, + { + label: i18n.translate('discover.noResults.kqlExamples.filterForDocsWithWildcardsText', { + defaultMessage: 'Filter for documents using wildcards', + }), + example: 'http.response.status_code: 4*', + }, + { + label: i18n.translate('discover.noResults.kqlExamples.negatingQueryText', { + defaultMessage: 'Negating a query', + }), + example: 'NOT http.request.method: GET', + }, + { + label: i18n.translate('discover.noResults.kqlExamples.combineMultipleText', { + defaultMessage: 'Combining multiple queries with AND/OR', + }), + example: 'http.request.method: GET AND http.response.status_code: 400', + }, + { + label: i18n.translate('discover.noResults.kqlExamples.queryMultipleText', { + defaultMessage: 'Querying multiple values for the same field', + }), + example: 'http.request.method: (GET OR POST OR DELETE)', + }, + ], + footer: ( + + + + ), + }} + /> + ), + }; + } + + return null; +}; + +export interface NoResultsSuggestionWhenQueryProps { + querySyntax: string | undefined; } + +export const NoResultsSuggestionWhenQuery: React.FC = ({ + querySyntax, +}) => { + const services = useDiscoverServices(); + const { docLinks } = services; + const examplesMeta = getExamples(querySyntax, docLinks); + + return ( + <> + + + + {examplesMeta ? ( + + ) : ( + + )} + + + {!!examplesMeta && ( + + + + )} + + + ); +}; diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_time_range.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_time_range.tsx index 434d6025b950e..41f36b446778e 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_time_range.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestion_when_time_range.tsx @@ -8,27 +8,15 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { - EuiDescriptionList, - EuiDescriptionListTitle, - EuiDescriptionListDescription, -} from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; -export function NoResultsSuggestionWhenTimeRange() { +export const NoResultsSuggestionWhenTimeRange: React.FC = () => { return ( - - - - - - - - + + + ); -} +}; diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx index 595ca61225ebb..e9cd75e022db3 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/no_results_suggestions.tsx @@ -6,8 +6,18 @@ * Side Public License, v 1. */ -import React from 'react'; -import { EuiSpacer } from '@elastic/eui'; +import React, { useState } from 'react'; +import { css } from '@emotion/react'; +import { EuiEmptyPrompt, EuiButton, EuiLoadingSpinner, EuiSpacer, useEuiTheme } from '@elastic/eui'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import { + isOfQueryType, + isOfAggregateQueryType, + type Query, + type AggregateQuery, + type Filter, +} from '@kbn/es-query'; +import { FormattedMessage } from '@kbn/i18n-react'; import { NoResultsSuggestionDefault } from './no_results_suggestion_default'; import { NoResultsSuggestionWhenFilters, @@ -15,41 +25,133 @@ import { } from './no_results_suggestion_when_filters'; import { NoResultsSuggestionWhenQuery } from './no_results_suggestion_when_query'; import { NoResultsSuggestionWhenTimeRange } from './no_results_suggestion_when_time_range'; +import { hasActiveFilter } from '../../layout/utils'; +import { useDiscoverServices } from '../../../../../hooks/use_discover_services'; +import { useFetchOccurrencesRange } from './use_fetch_occurances_range'; +import { NoResultsIllustration } from './assets/no_results_illustration'; interface NoResultsSuggestionProps { - hasFilters?: boolean; - hasQuery?: boolean; + dataView: DataView; isTimeBased?: boolean; + query: Query | AggregateQuery | undefined; + filters: Filter[] | undefined; onDisableFilters: NoResultsSuggestionWhenFiltersProps['onDisableFilters']; } -export function NoResultsSuggestions({ +export const NoResultsSuggestions: React.FC = ({ + dataView, isTimeBased, - hasFilters, - hasQuery, + query, + filters, onDisableFilters, -}: NoResultsSuggestionProps) { +}) => { + const { euiTheme } = useEuiTheme(); + const services = useDiscoverServices(); + const { data, uiSettings, timefilter } = services; + const hasQuery = + (isOfQueryType(query) && !!query?.query) || (!!query && isOfAggregateQueryType(query)); + const hasFilters = hasActiveFilter(filters); + + const [isExtending, setIsExtending] = useState(false); + const { range: occurrencesRange, refetch } = useFetchOccurrencesRange({ + dataView, + query, + filters, + services: { + data, + uiSettings, + }, + }); + + const extendTimeRange = async () => { + setIsExtending(true); + const range = await refetch(); + if (range?.from && range?.to) { + timefilter.setTime({ + from: range.from, + to: range.to, + }); + } else { + setIsExtending(false); + } + }; + + const canExtendTimeRange = Boolean(occurrencesRange?.from && occurrencesRange.to); const canAdjustSearchCriteria = isTimeBased || hasFilters || hasQuery; - if (canAdjustSearchCriteria) { - return ( - <> - {isTimeBased && } + const body = canAdjustSearchCriteria ? ( + <> + + +
        + {isTimeBased && ( +
      • + +
      • + )} {hasQuery && ( - <> - - - +
      • + +
      • )} {hasFilters && ( - <> - +
      • - +
      • )} - - ); - } +
      + + ) : ( + + ); - return ; -} + return ( + } + title={ +

      + +

      + } + body={body} + actions={ +
      + {typeof occurrencesRange === 'undefined' ? ( + + ) : canExtendTimeRange ? ( + + + + ) : null} +
      + } + /> + ); +}; diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/syntax_suggestions_popover.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/syntax_suggestions_popover.tsx new file mode 100644 index 0000000000000..4e5e3ba3796cd --- /dev/null +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/syntax_suggestions_popover.tsx @@ -0,0 +1,102 @@ +/* + * 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 React, { useState } from 'react'; +import { css } from '@emotion/react'; +import { + EuiBasicTable, + EuiButtonIcon, + EuiPanel, + EuiPopover, + EuiPopoverTitle, + EuiCode, + EuiText, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +interface SyntaxExample { + label: string; + example: string; +} + +export interface SyntaxExamples { + title: string; + footer: React.ReactElement; + items: SyntaxExample[]; +} + +export interface SyntaxSuggestionsPopoverProps { + meta: SyntaxExamples; +} + +export const SyntaxSuggestionsPopover: React.FC = ({ meta }) => { + const [isOpen, setIsOpen] = useState(false); + const { title, items, footer } = meta; + + const helpButton = ( + setIsOpen((prev) => !prev)} + iconType="documentation" + aria-label={title} + /> + ); + + const columns = [ + { + field: 'label', + name: i18n.translate('discover.noResults.suggestion.syntaxPopoverDescriptionHeader', { + defaultMessage: 'Description', + }), + width: '200px', + }, + { + field: 'example', + name: i18n.translate('discover.noResults.suggestion.syntaxPopoverExampleHeader', { + defaultMessage: 'Example', + }), + render: (example: string) => {example}, + }, + ]; + + return ( + setIsOpen(false)} + initialFocus="#querySyntaxBasicTableId" + > + {title} + + + id="querySyntaxBasicTableId" + tableCaption={title} + items={items} + compressed={true} + rowHeader="label" + columns={columns} + responsive + /> + + + {footer} + + + + ); +}; diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts new file mode 100644 index 0000000000000..8ae801fc93039 --- /dev/null +++ b/src/plugins/discover/public/application/main/components/no_results/no_results_suggestions/use_fetch_occurances_range.ts @@ -0,0 +1,153 @@ +/* + * 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 { useCallback, useEffect, useRef, useState } from 'react'; +import { lastValueFrom } from 'rxjs'; +import type { DataView } from '@kbn/data-plugin/common'; +import type { AggregateQuery, Filter, Query } from '@kbn/es-query'; +import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; +import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; +import type { AggregationsSingleMetricAggregateBase } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { buildEsQuery } from '@kbn/es-query'; +import { getEsQueryConfig } from '@kbn/data-plugin/common'; + +export interface Params { + dataView?: DataView; + query?: Query | AggregateQuery; + filters?: Filter[]; + services: { + data: DataPublicPluginStart; + uiSettings: IUiSettingsClient; + }; +} + +export interface OccurrencesRange { + from: string; + to: string; +} + +export interface Result { + range: OccurrencesRange | null | undefined; + refetch: () => Promise; +} + +export const useFetchOccurrencesRange = (params: Params): Result => { + const data = params.services.data; + const uiSettings = params.services.uiSettings; + const [range, setRange] = useState(undefined); + const abortControllerRef = useRef(null); + const mountedRef = useRef(true); + + const fetchOccurrences = useCallback( + async (dataView?: DataView, query?: Query | AggregateQuery, filters?: Filter[]) => { + let occurrencesRange = null; + if (!dataView?.timeFieldName || !query || !mountedRef.current) { + return null; + } + + abortControllerRef.current?.abort(); + abortControllerRef.current = new AbortController(); + + try { + const dslQuery = buildEsQuery( + dataView, + query ?? [], + filters ?? [], + getEsQueryConfig(uiSettings) + ); + occurrencesRange = await fetchDocumentsTimeRange({ + data, + dataView, + dslQuery, + abortSignal: abortControllerRef.current?.signal, + }); + } catch (error) { + // + } + + if (mountedRef.current) { + setRange(occurrencesRange); + } + + return occurrencesRange; + }, + [abortControllerRef, setRange, mountedRef, data, uiSettings] + ); + + useEffect(() => { + return () => { + mountedRef.current = false; + abortControllerRef.current?.abort(); + }; + }, [abortControllerRef, mountedRef]); + + useEffect(() => { + fetchOccurrences(params.dataView, params.query, params.filters); + }, [fetchOccurrences, params.query, params.filters, params.dataView]); + + return { + range, + refetch: () => fetchOccurrences(params.dataView, params.query, params.filters), + }; +}; + +async function fetchDocumentsTimeRange({ + data, + dataView, + dslQuery, + abortSignal, +}: { + data: DataPublicPluginStart; + dataView: DataView; + dslQuery?: object; + abortSignal?: AbortSignal; +}): Promise { + if (!dataView?.timeFieldName) { + return null; + } + + const result = await lastValueFrom( + data.search.search( + { + params: { + index: dataView.title, + size: 0, + body: { + query: dslQuery ?? { match_all: {} }, + aggs: { + earliest_timestamp: { + min: { + field: dataView.timeFieldName, + }, + }, + latest_timestamp: { + max: { + field: dataView.timeFieldName, + }, + }, + }, + }, + }, + }, + { + abortSignal, + } + ) + ); + + const earliestTimestamp = ( + result.rawResponse?.aggregations?.earliest_timestamp as AggregationsSingleMetricAggregateBase + )?.value_as_string; + const latestTimestamp = ( + result.rawResponse?.aggregations?.latest_timestamp as AggregationsSingleMetricAggregateBase + )?.value_as_string; + + return earliestTimestamp && latestTimestamp + ? { from: earliestTimestamp, to: latestTimestamp } + : null; +} diff --git a/src/plugins/discover/public/application/main/discover_main_route.tsx b/src/plugins/discover/public/application/main/discover_main_route.tsx index b317c0e5cffdc..242f2ab95a5cf 100644 --- a/src/plugins/discover/public/application/main/discover_main_route.tsx +++ b/src/plugins/discover/public/application/main/discover_main_route.tsx @@ -21,6 +21,7 @@ import { getSavedSearch, getSavedSearchFullPathUrl, } from '@kbn/saved-search-plugin/public'; +import useObservable from 'react-use/lib/useObservable'; import { MainHistoryLocationState } from '../../../common/locator'; import { getDiscoverStateContainer } from './services/discover_state'; import { loadDataView, resolveDataView } from './utils/resolve_data_view'; @@ -63,6 +64,7 @@ export function DiscoverMainRoute(props: Props) { const [hasESData, setHasESData] = useState(false); const [hasUserDataView, setHasUserDataView] = useState(false); const [showNoDataPage, setShowNoDataPage] = useState(false); + const hasCustomBranding = useObservable(core.customBranding.hasCustomBranding$, false); const { id } = useParams(); /** @@ -278,7 +280,7 @@ export function DiscoverMainRoute(props: Props) { } if (loading || !savedSearch) { - return ; + return ; } return ; diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 7b0fd713c09cc..de86ad4048478 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -18,7 +18,7 @@ import { testGuideConfig, testGuideId } from '@kbn/guided-onboarding'; import type { PluginState } from '../../common'; import { API_BASE_PATH } from '../../common'; -import { apiService } from '../services/api'; +import { apiService } from '../services/api.service'; import type { GuidedOnboardingApi } from '../types'; import { testGuideStep1ActiveState, diff --git a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx index 6765e7a4ecbf9..c649ca2a9eae0 100644 --- a/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx +++ b/src/plugins/guided_onboarding/public/components/quit_guide_modal.tsx @@ -20,7 +20,7 @@ import { import { i18n } from '@kbn/i18n'; import type { GuideState } from '@kbn/guided-onboarding'; import { NotificationsStart } from '@kbn/core/public'; -import { apiService } from '../services/api'; +import { apiService } from '../services/api.service'; interface QuitGuideModalProps { closeModal: () => void; diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index 4c5e02ddac9c4..97eac765c5ddd 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -27,7 +27,7 @@ import type { GuidedOnboardingPluginStart, } from './types'; import { GuidePanel } from './components'; -import { ApiService, apiService } from './services/api'; +import { ApiService, apiService } from './services/api.service'; export class GuidedOnboardingPlugin implements Plugin diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.service.ts similarity index 97% rename from src/plugins/guided_onboarding/public/services/api.ts rename to src/plugins/guided_onboarding/public/services/api.service.ts index 5ab9bed187f6b..398d1cf56a6a3 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.service.ts @@ -37,7 +37,7 @@ import { getStepConfig, isLastStep, } from './helpers'; -import { ConfigService } from './config_service'; +import { ConfigService } from './config.service'; export class ApiService implements GuidedOnboardingApi { private isCloudEnabled: boolean | undefined; @@ -123,6 +123,10 @@ export class ApiService implements GuidedOnboardingApi { if (!this.client) { throw new Error('ApiService has not be initialized.'); } + // don't send a request if a request is already in flight + if (this.isLoading$.value) { + return undefined; + } try { this.isLoading$.next(true); @@ -152,6 +156,10 @@ export class ApiService implements GuidedOnboardingApi { if (!this.client) { throw new Error('ApiService has not be initialized.'); } + // don't send a request if a request is already in flight + if (this.isLoading$.value) { + return undefined; + } try { this.isLoading$.next(true); @@ -474,6 +482,10 @@ export class ApiService implements GuidedOnboardingApi { if (!this.client) { throw new Error('ApiService has not be initialized.'); } + // don't send a request if a request is already in flight + if (this.isLoading$.value) { + return undefined; + } this.isLoading$.next(true); const config = await this.configService.getGuideConfig(guideId); this.isLoading$.next(false); diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 5146d2df72ddb..1b5a7b02d0bb3 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -13,7 +13,7 @@ import { testGuideConfig, testGuideId } from '@kbn/guided-onboarding'; import { firstValueFrom, Subscription } from 'rxjs'; import { API_BASE_PATH } from '../../common'; -import { ApiService } from './api'; +import { ApiService } from './api.service'; import { testGuideFirstStep, testGuideLastStep, diff --git a/src/plugins/guided_onboarding/public/services/config_service.test.ts b/src/plugins/guided_onboarding/public/services/config.service.test.ts similarity index 98% rename from src/plugins/guided_onboarding/public/services/config_service.test.ts rename to src/plugins/guided_onboarding/public/services/config.service.test.ts index 98aa0deb35701..42ddcb5e46808 100644 --- a/src/plugins/guided_onboarding/public/services/config_service.test.ts +++ b/src/plugins/guided_onboarding/public/services/config.service.test.ts @@ -18,7 +18,7 @@ import { wrongIntegration, } from './api.mocks'; -import { ConfigService } from './config_service'; +import { ConfigService } from './config.service'; describe('GuidedOnboarding ConfigService', () => { let configService: ConfigService; diff --git a/src/plugins/guided_onboarding/public/services/config_service.ts b/src/plugins/guided_onboarding/public/services/config.service.ts similarity index 100% rename from src/plugins/guided_onboarding/public/services/config_service.ts rename to src/plugins/guided_onboarding/public/services/config.service.ts diff --git a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap index 4483bc431241e..291d0173a0bbf 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap +++ b/src/plugins/home/public/application/components/guided_onboarding/__snapshots__/getting_started.test.tsx.snap @@ -36,7 +36,7 @@ exports[`getting started should render getting started component 1`] = ` textAlign="center" >

      - Select a guide to help you make the most of your data. + Select an option and we'll help you get started.

      - - - - - - - - - - - - - - - - + + +
      - I’d like to do something else (skip) + I’d like to do something else.
      diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx index 9506c713faf90..a575ed1d0d82a 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.test.tsx @@ -12,8 +12,7 @@ import { act } from 'react-dom/test-utils'; import { findTestSubject, registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { chromeServiceMock, applicationServiceMock, httpServiceMock } from '@kbn/core/public/mocks'; -import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; -import { ApiService } from '@kbn/guided-onboarding-plugin/public/services/api'; +import { ApiService } from '@kbn/guided-onboarding-plugin/public/services/api.service'; import { GettingStarted } from './getting_started'; import { KEY_ENABLE_WELCOME } from '../home'; @@ -21,8 +20,6 @@ import { KEY_ENABLE_WELCOME } from '../home'; const mockCloud = cloudMock.createSetup(); const mockChrome = chromeServiceMock.createStartContract(); const mockApplication = applicationServiceMock.createStartContract(); -const mockSettingsUI = uiSettingsServiceMock.createSetupContract(); -mockSettingsUI.get.mockReturnValue(false); const mockHttp = httpServiceMock.createStartContract(); const mockApiService = new ApiService(); mockApiService.setup(mockHttp, true); @@ -33,8 +30,6 @@ jest.mock('../../kibana_services', () => ({ chrome: mockChrome, application: mockApplication, trackUiMetric: jest.fn(), - uiSettings: mockSettingsUI, - http: mockHttp, guidedOnboardingService: mockApiService, }), })); diff --git a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx index 2af90e9be5edb..01d39efbcf2b8 100644 --- a/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx +++ b/src/plugins/home/public/application/components/guided_onboarding/getting_started.tsx @@ -9,9 +9,6 @@ import React, { useCallback, useEffect, useState } from 'react'; import { EuiButton, - EuiFlexGrid, - EuiFlexItem, - EuiHorizontalRule, EuiLink, EuiLoadingSpinner, EuiPageTemplate, @@ -26,9 +23,9 @@ import { useHistory } from 'react-router-dom'; import { METRIC_TYPE } from '@kbn/analytics'; import { i18n } from '@kbn/i18n'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -import type { GuideState, GuideId, GuideCardUseCase } from '@kbn/guided-onboarding'; -import { GuideCard, InfrastructureLinkCard } from '@kbn/guided-onboarding'; +import type { GuideFilterValues, GuideId, GuideState } from '@kbn/guided-onboarding'; +import { GuideCards, GuideFilters } from '@kbn/guided-onboarding'; import { getServices } from '../../kibana_services'; import { KEY_ENABLE_WELCOME } from '../home'; @@ -40,18 +37,18 @@ const title = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelect defaultMessage: 'What would you like to do first?', }); const subtitle = i18n.translate('home.guidedOnboarding.gettingStarted.useCaseSelectionSubtitle', { - defaultMessage: 'Select a guide to help you make the most of your data.', + defaultMessage: `Select an option and we'll help you get started.`, }); const skipText = i18n.translate('home.guidedOnboarding.gettingStarted.skip.buttonLabel', { - defaultMessage: `I’d like to do something else (skip)`, + defaultMessage: `I’d like to do something else.`, }); export const GettingStarted = () => { - const { application, trackUiMetric, chrome, guidedOnboardingService, http, uiSettings, cloud } = - getServices(); + const { application, trackUiMetric, chrome, guidedOnboardingService, cloud } = getServices(); const [guidesState, setGuidesState] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); + const [filter, setFilter] = useState('all'); const history = useHistory(); useEffect(() => { @@ -114,11 +111,10 @@ export const GettingStarted = () => { padding: calc(${euiTheme.size.base}*3) calc(${euiTheme.size.base}*4); `; - const isDarkTheme = uiSettings.get('theme:darkMode'); const activateGuide = useCallback( - async (useCase: GuideCardUseCase, guideState?: GuideState) => { + async (guideId: GuideId, guideState?: GuideState) => { try { - await guidedOnboardingService?.activateGuide(useCase as GuideId, guideState); + await guidedOnboardingService?.activateGuide(guideId, guideState); } catch (err) { getServices().toastNotifications.addDanger({ title: i18n.translate('home.guidedOnboarding.gettingStarted.activateGuide.errorMessage', { @@ -200,34 +196,14 @@ export const GettingStarted = () => { - - {['search', 'kubernetes', 'infrastructure', 'siem'].map((useCase) => { - if (useCase === 'infrastructure') { - return ( - - - - ); - } - return ( - - - - ); - })} - - - + + +
      {/* data-test-subj used for FS tracking */} diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json index 693c8189c504f..904cf7194c390 100644 --- a/src/plugins/home/tsconfig.json +++ b/src/plugins/home/tsconfig.json @@ -23,7 +23,6 @@ "@kbn/shared-ux-page-kibana-template", "@kbn/utility-types", "@kbn/guided-onboarding", - "@kbn/core-ui-settings-browser-mocks", "@kbn/ui-theme", "@kbn/config-schema", "@kbn/utility-types-jest", diff --git a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap index 3ac183860d215..453d20385e706 100644 --- a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap +++ b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap @@ -457,6 +457,7 @@ exports[`Overview renders correctly when there is no user data view 1`] = ` "navigateToUrl": [MockFunction], }, "chrome": undefined, + "customBranding": undefined, "docLinks": Object { "links": Object { "kibana": Object { diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx index f87c90a4591d4..f6d97d54681e8 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx @@ -62,8 +62,17 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => const [hasDataView, setHasDataView] = useState(false); const [isLoading, setIsLoading] = useState(true); const { services } = useKibana(); - const { http, docLinks, dataViews, share, uiSettings, application, chrome, dataViewEditor } = - services; + const { + http, + docLinks, + dataViews, + share, + uiSettings, + application, + chrome, + dataViewEditor, + customBranding, + } = services; const addBasePath = http.basePath.prepend; const IS_DARK_THEME = uiSettings.get('theme:darkMode'); @@ -177,6 +186,7 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => chrome, docLinks, http, + customBranding, }, dataViews: { ...dataViews, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index ec389fda1f084..14d104d84e334 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -570,8 +570,4 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'enterpriseSearch:enableEnginesSection': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 6a0743256465f..8103c0e93f301 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -152,5 +152,4 @@ export interface UsageStats { 'securitySolution:enableGroupedNav': boolean; 'securitySolution:showRelatedIntegrations': boolean; 'visualization:visualize:legacyGaugeChartsLibrary': boolean; - 'enterpriseSearch:enableEnginesSection': boolean; } diff --git a/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap b/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap index dfd1cf4dbcf5e..6b520e5e74346 100644 --- a/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap +++ b/src/plugins/newsfeed/public/components/__snapshots__/loading_news.test.tsx.snap @@ -1,5 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`news_loading rendering renders the News Loading with EuiLoadingSpinner 1`] = ` + + +

      + } + title={ + + } +/> +`; + exports[`news_loading rendering renders the default News Loading 1`] = ` ) => { +export const NewsfeedFlyout = (props: Partial & { showPlainSpinner: boolean }) => { const { newsFetchResult, setFlyoutVisible } = useContext(NewsfeedContext); const closeFlyout = useCallback(() => setFlyoutVisible(false), [setFlyoutVisible]); @@ -55,7 +55,7 @@ export const NewsfeedFlyout = (props: Partial) => { {!newsFetchResult ? ( - + ) : newsFetchResult.feedItems.length > 0 ? ( newsFetchResult.feedItems.map((item: NewsfeedItem) => { return ( diff --git a/src/plugins/newsfeed/public/components/loading_news.test.tsx b/src/plugins/newsfeed/public/components/loading_news.test.tsx index 6c857d9a8d803..1d5227e47d940 100644 --- a/src/plugins/newsfeed/public/components/loading_news.test.tsx +++ b/src/plugins/newsfeed/public/components/loading_news.test.tsx @@ -14,7 +14,11 @@ import { NewsLoadingPrompt } from './loading_news'; describe('news_loading', () => { describe('rendering', () => { it('renders the default News Loading', () => { - const wrapper = shallow(); + const wrapper = shallow(); + expect(toJson(wrapper)).toMatchSnapshot(); + }); + it('renders the News Loading with EuiLoadingSpinner', () => { + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/src/plugins/newsfeed/public/components/loading_news.tsx b/src/plugins/newsfeed/public/components/loading_news.tsx index d268f67758edb..c596784f49335 100644 --- a/src/plugins/newsfeed/public/components/loading_news.tsx +++ b/src/plugins/newsfeed/public/components/loading_news.tsx @@ -9,13 +9,13 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui'; import { EuiLoadingElastic } from '@elastic/eui'; -export const NewsLoadingPrompt = () => { +export const NewsLoadingPrompt = ({ showPlainSpinner }: { showPlainSpinner: boolean }) => { return ( } + title={showPlainSpinner ? : } body={

      ; } -export const NewsfeedNavButton = ({ newsfeedApi }: Props) => { +export const NewsfeedNavButton = ({ newsfeedApi, hasCustomBranding$ }: Props) => { const [flyoutVisible, setFlyoutVisible] = useState(false); const [newsFetchResult, setNewsFetchResult] = useState(null); + const hasCustomBranding = useObservable(hasCustomBranding$, false); const hasNew = useMemo(() => { return newsFetchResult ? newsFetchResult.hasNew : false; }, [newsFetchResult]); @@ -71,7 +75,12 @@ export const NewsfeedNavButton = ({ newsfeedApi }: Props) => { > - {flyoutVisible ? : null} + {flyoutVisible ? ( + + ) : null} ); diff --git a/src/plugins/newsfeed/public/plugin.tsx b/src/plugins/newsfeed/public/plugin.tsx index 344e99ee8e484..cb3ab294616ba 100644 --- a/src/plugins/newsfeed/public/plugin.tsx +++ b/src/plugins/newsfeed/public/plugin.tsx @@ -55,7 +55,8 @@ export class NewsfeedPublicPlugin const api = this.createNewsfeedApi(this.config, NewsfeedApiEndpoint.KIBANA, isScreenshotMode); core.chrome.navControls.registerRight({ order: 1000, - mount: (target) => this.mount(api, target, core.theme.theme$), + mount: (target) => + this.mount(api, target, core.theme.theme$, core.customBranding.hasCustomBranding$), }); return { @@ -91,11 +92,16 @@ export class NewsfeedPublicPlugin }; } - private mount(api: NewsfeedApi, targetDomElement: HTMLElement, theme$: Rx.Observable) { + private mount( + api: NewsfeedApi, + targetDomElement: HTMLElement, + theme$: Rx.Observable, + hasCustomBranding$: Rx.Observable + ) { ReactDOM.render( - + , targetDomElement diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 1219d8c7aad4e..ddc16fba2319a 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -9113,12 +9113,6 @@ "_meta": { "description": "Non-default value of setting." } - }, - "enterpriseSearch:enableEnginesSection": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } } } }, diff --git a/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx b/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx index 7cabd28bc171d..9a86b56f52dc7 100644 --- a/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx +++ b/src/plugins/unified_field_list/public/components/field_list_grouped/field_list_grouped.test.tsx @@ -369,7 +369,7 @@ describe('UnifiedFieldList + useGroupedFields()', () => { expect(wrapper.find(`#${defaultProps.screenReaderDescriptionId}`).first().text()).toBe( '1 available field. 4 unmapped fields. 0 empty fields. 0 meta fields.' ); - }); + }, 10000); it('renders correctly when non-supported fields are filtered out', async () => { const hookParams = { diff --git a/src/plugins/unified_search/public/filter_badge/filter_badge_expression.tsx b/src/plugins/unified_search/public/filter_badge/filter_badge_expression.tsx index d01f7977c6b98..1fb9b1353087b 100644 --- a/src/plugins/unified_search/public/filter_badge/filter_badge_expression.tsx +++ b/src/plugins/unified_search/public/filter_badge/filter_badge_expression.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { getDisplayValueFromFilter, getFieldDisplayValueFromFilter } from '@kbn/data-plugin/public'; import type { Filter, DataViewBase } from '@kbn/es-query'; +import { isCombinedFilter } from '@kbn/es-query'; import { EuiTextColor } from '@elastic/eui'; import { FilterBadgeGroup } from './filter_badge_group'; import { FilterContent } from './filter_content'; @@ -54,9 +55,10 @@ export function FilterExpressionBadge({ dataViews, filterLabelStatus, }: FilterBadgeExpressionProps) { + const isCombined = isCombinedFilter(filter); const conditionalOperationType = getBooleanRelationType(filter); - return conditionalOperationType ? ( + return conditionalOperationType && isCombined ? ( <> {shouldShowBrackets && ( diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx index 809544c017224..ab33242992067 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/filter_editor.tsx @@ -32,6 +32,7 @@ import { buildEmptyFilter, filterToQueryDsl, getFilterParams, + isCombinedFilter, } from '@kbn/es-query'; import { merge } from 'lodash'; import React, { Component } from 'react'; @@ -390,9 +391,10 @@ class FilterEditorComponent extends Component { }; private isUnknownFilterType() { - const { type, params } = this.props.filter.meta; - if (params && type === 'combined') { - return this.hasCombinedFilterCustomType(params); + const { type } = this.props.filter.meta; + if (isCombinedFilter(this.props.filter)) { + const { params } = this.props.filter.meta; + return params && this.hasCombinedFilterCustomType(params); } return !!type && !['phrase', 'phrases', 'range', 'exists', 'combined'].includes(type); } diff --git a/src/plugins/unified_search/public/filters_builder/filter_group.tsx b/src/plugins/unified_search/public/filters_builder/filter_group.tsx index daaefd6f90372..cad3cee7a9063 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_group.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_group.tsx @@ -17,7 +17,7 @@ import { useEuiPaddingSize, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { type Filter, BooleanRelation } from '@kbn/es-query'; +import { BooleanRelation, type Filter } from '@kbn/es-query'; import { cx } from '@emotion/css'; import type { Path } from './types'; import { getBooleanRelationType } from '../utils'; @@ -85,9 +85,17 @@ export const FilterGroup = ({ const orDisabled = hideOr || (isDepthReached && booleanRelation === BooleanRelation.AND); const andDisabled = isDepthReached && booleanRelation === BooleanRelation.OR; - const removeDisabled = pathInArray.length <= 1 && filters.length === 1; + const removeDisabled = + pathInArray.length <= 1 && + filters !== undefined && + Array.isArray(filters) && + filters.length === 1; const shouldNormalizeFirstLevel = - !path && filters.length === 1 && getBooleanRelationType(filters[0]); + !path && + filters && + Array.isArray(filters) && + filters.length === 1 && + getBooleanRelationType(filters[0]); if (shouldNormalizeFirstLevel) { reverseBackground = true; @@ -96,38 +104,41 @@ export const FilterGroup = ({ const color = reverseBackground ? 'plain' : 'subdued'; - const renderedFilters = filters.map((filter, index, arrayRef) => { - const showDelimiter = booleanRelation && index + 1 < arrayRef.length; - return ( - - - - - - {showDelimiter && ( + const renderedFilters = + filters && + Array.isArray(filters) && + filters.map((filter, index, arrayRef) => { + const showDelimiter = booleanRelation && index + 1 < arrayRef.length; + return ( + - + - )} - - ); - }); + + {showDelimiter && ( + + + + )} + + ); + }); return shouldNormalizeFirstLevel ? ( <>{renderedFilters} diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx index 071a35b289c91..b24429860d416 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_item/filter_item.tsx @@ -33,6 +33,7 @@ import { FilterGroup } from '../filter_group'; import type { Path } from '../types'; import { getFieldFromFilter, getOperatorFromFilter } from '../../filter_bar/filter_editor'; import { Operator } from '../../filter_bar/filter_editor'; +import { getGroupedFilters } from '../utils/filters_builder'; import { cursorAddCss, cursorOrCss, @@ -101,7 +102,7 @@ export function FilterItem({ const { euiTheme } = useEuiTheme(); let field: DataViewField | undefined; let operator: Operator | undefined; - let params: Filter['meta']['params'] | undefined; + let params: Filter['meta']['params']; const isMaxNesting = isMaxFilterNesting(path); if (!conditionalOperationType) { field = getFieldFromFilter(filter, dataView!); @@ -132,7 +133,7 @@ export function FilterItem({ ); const onHandleParamsChange = useCallback( - (selectedParams: unknown) => { + (selectedParams: Filter['meta']['params']) => { dispatch({ type: 'updateFilter', payload: { dest: { path, index }, field, operator, params: selectedParams }, @@ -146,7 +147,12 @@ export function FilterItem({ const paramsValues = Array.isArray(params) ? params : []; dispatch({ type: 'updateFilter', - payload: { dest: { path, index }, field, operator, params: [...paramsValues, value] }, + payload: { + dest: { path, index }, + field, + operator, + params: [...paramsValues, value] as Filter['meta']['params'], + }, }); }, [dispatch, path, index, field, operator, params] @@ -192,7 +198,7 @@ export function FilterItem({ diff --git a/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx b/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx index a6b1feb46c551..d2ac16e17ebdc 100644 --- a/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx +++ b/src/plugins/unified_search/public/filters_builder/filter_item/params_editor.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useContext } from 'react'; import { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import type { Filter } from '@kbn/es-query'; import { EuiToolTip, EuiFormRow } from '@elastic/eui'; import type { Operator } from '../../filter_bar/filter_editor'; import { getFieldValidityAndErrorMessage } from '../../filter_bar/filter_editor/lib'; @@ -17,8 +18,8 @@ import { ParamsEditorInput } from './params_editor_input'; interface ParamsEditorProps { dataView: DataView; params: unknown; - onHandleParamsChange: (params: unknown) => void; - onHandleParamsUpdate: (value: unknown) => void; + onHandleParamsChange: (params: Filter['meta']['params']) => void; + onHandleParamsUpdate: (value: string) => void; timeRangeForSuggestionsOverride?: boolean; field?: DataViewField; operator?: Operator; diff --git a/src/plugins/unified_search/public/filters_builder/utils/filters_builder.test.ts b/src/plugins/unified_search/public/filters_builder/utils/filters_builder.test.ts index 534237b0ff7bc..337722a756faa 100644 --- a/src/plugins/unified_search/public/filters_builder/utils/filters_builder.test.ts +++ b/src/plugins/unified_search/public/filters_builder/utils/filters_builder.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { buildEmptyFilter, type Filter, BooleanRelation } from '@kbn/es-query'; +import { buildEmptyFilter, type Filter, isCombinedFilter, BooleanRelation } from '@kbn/es-query'; import { DataView } from '@kbn/data-views-plugin/common'; import { getFilterByPath, @@ -188,7 +188,19 @@ describe('filters_builder', () => { beforeAll(() => { filter = filters[0]; filtersWithOrRelationships = filters[1]; - groupOfFilters = filters[1].meta.params[1]; + if (Array.isArray(filters[1].meta.params)) { + const secondFilter = filters[1].meta.params[1]; + if ( + typeof secondFilter !== 'number' && + typeof secondFilter !== 'string' && + typeof secondFilter !== 'boolean' && + isCombinedFilter(secondFilter) + ) { + groupOfFilters = secondFilter; + } + } else { + groupOfFilters = filters[0]; + } }); test('should return correct ConditionalOperationType', () => { diff --git a/src/plugins/unified_search/public/filters_builder/utils/filters_builder.ts b/src/plugins/unified_search/public/filters_builder/utils/filters_builder.ts index 63994fbc1d4e6..212754e891f15 100644 --- a/src/plugins/unified_search/public/filters_builder/utils/filters_builder.ts +++ b/src/plugins/unified_search/public/filters_builder/utils/filters_builder.ts @@ -19,8 +19,14 @@ const PATH_SEPARATOR = '.'; export const getPathInArray = (path: Path) => path.split(PATH_SEPARATOR).map(Number); -const getGroupedFilters = (filter: Filter): Filter[] => - Array.isArray(filter) ? filter : filter?.meta?.params ?? []; +export const getGroupedFilters = (filter: Filter): Filter[] => { + const isCombined = isCombinedFilter(filter); + if (isCombined) { + return filter?.meta?.params ?? []; + } else { + return []; + } +}; const doForFilterByPath = (filters: Filter[], path: Path, action: (filter: Filter) => T) => { const [first, ...restPath] = getPathInArray(path); diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js index 66a01793a0e96..72aee7fa575cf 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/table/vis.js @@ -46,8 +46,9 @@ function getColor(rules, colorKey, value) { } function sanitizeUrl(url) { + const { protocol } = parseUrl(url); // eslint-disable-next-line no-script-url - if (parseUrl(url).protocol === 'javascript:') { + if (protocol === 'javascript:' || protocol === 'data:' || protocol === 'vbscript:') { return ''; } return url; diff --git a/test/functional/apps/discover/group1/_discover.ts b/test/functional/apps/discover/group1/_discover.ts index 1cba5aa4812d8..c3be725737470 100644 --- a/test/functional/apps/discover/group1/_discover.ts +++ b/test/functional/apps/discover/group1/_discover.ts @@ -159,6 +159,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const isVisible = await PageObjects.discover.hasNoResultsTimepicker(); expect(isVisible).to.be(true); }); + + it('should show matches when time range is expanded', async () => { + await PageObjects.discover.expandTimeRangeAsSuggestedInNoResultsMessage(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await retry.try(async function () { + expect(await PageObjects.discover.hasNoResults()).to.be(false); + expect(await PageObjects.discover.getHitCountInt()).to.be.above(0); + }); + }); }); describe('nested query', () => { diff --git a/test/functional/apps/discover/group1/_sidebar.ts b/test/functional/apps/discover/group1/_sidebar.ts index 9d0878b107073..d57b3edfe83e0 100644 --- a/test/functional/apps/discover/group1/_sidebar.ts +++ b/test/functional/apps/discover/group1/_sidebar.ts @@ -577,6 +577,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { '_bytes-runtimefield', `emit((doc["bytes"].value * 2).toString())` ); + + await retry.waitFor('form to close', async () => { + return !(await testSubjects.exists('fieldEditor')); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSidebarHasLoaded(); @@ -592,6 +597,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await fieldEditor.setCustomLabel('_bytes-runtimefield2'); await fieldEditor.save(); + await retry.waitFor('form to close', async () => { + return !(await testSubjects.exists('fieldEditor')); + }); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.discover.waitUntilSidebarHasLoaded(); diff --git a/test/functional/apps/discover/group2/_adhoc_data_views.ts b/test/functional/apps/discover/group2/_adhoc_data_views.ts index 004eba75d3b6a..9699dc36dd81c 100644 --- a/test/functional/apps/discover/group2/_adhoc_data_views.ts +++ b/test/functional/apps/discover/group2/_adhoc_data_views.ts @@ -272,12 +272,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); const [firstToast, secondToast] = await toasts.getAllToastElements(); - expect(await firstToast.getVisibleText()).to.equal( - `"${first}" is not a configured data view ID\nShowing the saved data view: "logstas*" (${second})` - ); - expect(await secondToast.getVisibleText()).to.equal( - `Different index references\nData view id references in some of the applied filters differ from the current data view.` + expect([await firstToast.getVisibleText(), await secondToast.getVisibleText()].sort()).to.eql( + [ + `"${first}" is not a configured data view ID\nShowing the saved data view: "logstas*" (${second})`, + `Different index references\nData view id references in some of the applied filters differ from the current data view.`, + ].sort() ); }); }); diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 46d2bd94423f9..8d4438ea91e1b 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -457,6 +457,13 @@ export class DiscoverPageObject extends FtrService { return await this.testSubjects.exists('discoverNoResultsTimefilter'); } + public async expandTimeRangeAsSuggestedInNoResultsMessage() { + await this.retry.waitFor('the button before pressing it', async () => { + return await this.testSubjects.exists('discoverNoResultsViewAllMatches'); + }); + return await this.testSubjects.click('discoverNoResultsViewAllMatches'); + } + public async getSidebarAriaDescription(): Promise { return await ( await this.testSubjects.find('fieldListGrouped__ariaDescription') diff --git a/test/functional/services/dashboard/add_panel.ts b/test/functional/services/dashboard/add_panel.ts index e42c221a49475..f3ee3cad65e1a 100644 --- a/test/functional/services/dashboard/add_panel.ts +++ b/test/functional/services/dashboard/add_panel.ts @@ -25,9 +25,14 @@ export class DashboardAddPanelService extends FtrService { async clickCreateNewLink() { this.log.debug('DashboardAddPanel.clickAddNewPanelButton'); - await this.testSubjects.click('dashboardAddNewPanelButton'); - // Give some time for the animation to complete - await this.common.sleep(500); + await this.retry.try(async () => { + await this.testSubjects.click('dashboardAddNewPanelButton'); + await this.testSubjects.waitForDeleted('dashboardAddNewPanelButton'); + await this.header.waitUntilLoadingHasFinished(); + await this.testSubjects.existOrFail('lnsApp', { + timeout: 5000, + }); + }); } async clickQuickButton(visType: string) { diff --git a/test/functional/services/filter_bar.ts b/test/functional/services/filter_bar.ts index 30cb97d3d6519..7e28176e764e7 100644 --- a/test/functional/services/filter_bar.ts +++ b/test/functional/services/filter_bar.ts @@ -302,7 +302,7 @@ export class FilterBarService extends FtrService { await this.createFilter(filter); - await this.testSubjects.clickWhenNotDisabledWithoutRetry('saveFilter'); + await this.testSubjects.clickWhenNotDisabled('saveFilter'); }); await this.header.awaitGlobalLoadingIndicatorHidden(); } diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index 9b17c97ae722e..ab0b708d59ee5 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerting/kibana.json @@ -8,7 +8,10 @@ }, "version": "8.0.0", "kibanaVersion": "kibana", - "configPath": ["xpack", "alerting"], + "configPath": [ + "xpack", + "alerting" + ], "requiredPlugins": [ "actions", "data", @@ -19,9 +22,16 @@ "features", "kibanaUtils", "licensing", - "spaces", "taskManager" ], - "optionalPlugins": ["usageCollection", "security", "monitoringCollection"], - "extraPublicDirs": ["common", "common/parse_duration"] -} + "optionalPlugins": [ + "usageCollection", + "security", + "monitoringCollection", + "spaces" + ], + "extraPublicDirs": [ + "common", + "common/parse_duration" + ] +} \ No newline at end of file diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts index 0ee4cce387d18..b0830a7127a38 100644 --- a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts @@ -16,7 +16,7 @@ export interface AlertingAuthorizationClientFactoryOpts { ruleTypeRegistry: RuleTypeRegistry; securityPluginSetup?: SecurityPluginSetup; securityPluginStart?: SecurityPluginStart; - getSpace: (request: KibanaRequest) => Promise; + getSpace: (request: KibanaRequest) => Promise; getSpaceId: (request: KibanaRequest) => string; features: FeaturesPluginStart; } @@ -26,7 +26,7 @@ export class AlertingAuthorizationClientFactory { private ruleTypeRegistry!: RuleTypeRegistry; private securityPluginStart?: SecurityPluginStart; private features!: FeaturesPluginStart; - private getSpace!: (request: KibanaRequest) => Promise; + private getSpace!: (request: KibanaRequest) => Promise; private getSpaceId!: (request: KibanaRequest) => string; public initialize(options: AlertingAuthorizationClientFactoryOpts) { diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts index 3d7f3b10390fe..11f57e8145e95 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts @@ -860,6 +860,45 @@ describe('AlertingEventLogger', () => { expect(alertingEventLogger.getEvent()).toEqual(loggedEvent); expect(eventLogger.logEvent).toHaveBeenCalledWith(loggedEvent); }); + + test('overwrites the message when the final status is error', () => { + alertingEventLogger.initialize(context); + alertingEventLogger.start(); + alertingEventLogger.setExecutionSucceeded('success message'); + + expect(alertingEventLogger.getEvent()!.message).toBe('success message'); + + alertingEventLogger.done({ + status: { + status: 'error', + lastExecutionDate: new Date(), + error: { reason: RuleExecutionStatusErrorReasons.Execute, message: 'failed execution' }, + }, + }); + + expect(alertingEventLogger.getEvent()!.message).toBe('test:123: execution failed'); + }); + + test('does not overwrites the message when there is already a failure message', () => { + alertingEventLogger.initialize(context); + alertingEventLogger.start(); + alertingEventLogger.setExecutionFailed('first failure message', 'failure error message'); + + expect(alertingEventLogger.getEvent()!.message).toBe('first failure message'); + + alertingEventLogger.done({ + status: { + status: 'error', + lastExecutionDate: new Date(), + error: { + reason: RuleExecutionStatusErrorReasons.Execute, + message: 'second failure execution', + }, + }, + }); + + expect(alertingEventLogger.getEvent()!.message).toBe('first failure message'); + }); }); }); diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts index 3422fb21bb1f9..fff026a358bc8 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts @@ -184,7 +184,7 @@ export class AlertingEventLogger { alertingOutcome: 'failure', reason: status.error?.reason || 'unknown', error: this.event?.error?.message || status.error.message, - ...(this.event.message + ...(this.event.message && this.event.event?.outcome === 'failure' ? {} : { message: `${this.ruleContext.ruleType.id}:${this.ruleContext.ruleId}: execution failed`, diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 5ae4be21d9a63..13f6e2ae1c9dc 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -20,6 +20,7 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { SpacesPluginStart } from '@kbn/spaces-plugin/server'; import { KibanaRequest, @@ -162,7 +163,7 @@ export interface AlertingPluginsStart { features: FeaturesPluginStart; eventLog: IEventLogClientService; licensing: LicensingPluginStart; - spaces: SpacesPluginStart; + spaces?: SpacesPluginStart; security?: SecurityPluginStart; data: DataPluginStart; dataViews: DataViewsPluginStart; @@ -417,10 +418,10 @@ export class AlertingPlugin { securityPluginSetup: security, securityPluginStart: plugins.security, async getSpace(request: KibanaRequest) { - return plugins.spaces.spacesService.getActiveSpace(request); + return plugins.spaces?.spacesService.getActiveSpace(request); }, getSpaceId(request: KibanaRequest) { - return plugins.spaces.spacesService.getSpaceId(request); + return plugins.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; }, features: plugins.features, }); @@ -434,7 +435,7 @@ export class AlertingPlugin { encryptedSavedObjectsClient, spaceIdToNamespace, getSpaceId(request: KibanaRequest) { - return plugins.spaces?.spacesService.getSpaceId(request); + return plugins.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; }, actions: plugins.actions, eventLog: plugins.eventLog, diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts index 20a5623edd392..3c97080e2d655 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts @@ -26,6 +26,8 @@ export async function validateActions( return; } + const errors = []; + // check for actions using connectors with missing secrets const actionsClient = await context.getActionsClient(); const actionIds = [...new Set(actions.map((action) => action.id))]; @@ -35,7 +37,7 @@ export async function validateActions( ); if (actionsUsingConnectorsWithMissingSecrets.length) { - throw Boom.badRequest( + errors.push( i18n.translate('xpack.alerting.rulesClient.validateActions.misconfiguredConnector', { defaultMessage: 'Invalid connectors: {groups}', values: { @@ -55,7 +57,7 @@ export async function validateActions( (group) => !availableAlertTypeActionGroups.has(group) ); if (invalidActionGroups.length) { - throw Boom.badRequest( + errors.push( i18n.translate('xpack.alerting.rulesClient.validateActions.invalidGroups', { defaultMessage: 'Invalid action groups: {groups}', values: { @@ -69,7 +71,7 @@ export async function validateActions( if (hasRuleLevelNotifyWhen || hasRuleLevelThrottle) { const actionsWithFrequency = actions.filter((action) => Boolean(action.frequency)); if (actionsWithFrequency.length) { - throw Boom.badRequest( + errors.push( i18n.translate('xpack.alerting.rulesClient.validateActions.mixAndMatchFreqParams', { defaultMessage: 'Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: {groups}', @@ -82,7 +84,7 @@ export async function validateActions( } else { const actionsWithoutFrequency = actions.filter((action) => !action.frequency); if (actionsWithoutFrequency.length) { - throw Boom.badRequest( + errors.push( i18n.translate('xpack.alerting.rulesClient.validateActions.notAllActionsWithFreq', { defaultMessage: 'Actions missing frequency parameters: {groups}', values: { @@ -101,7 +103,7 @@ export async function validateActions( parseDuration(action.frequency.throttle!) < scheduleInterval ); if (actionsWithInvalidThrottles.length) { - throw Boom.badRequest( + errors.push( i18n.translate('xpack.alerting.rulesClient.validateActions.actionsWithInvalidThrottles', { defaultMessage: 'Action throttle cannot be shorter than the schedule interval of {scheduleIntervalText}: {groups}', @@ -114,4 +116,18 @@ export async function validateActions( }) ); } + + // Finalize and throw any errors present + if (errors.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.errorSummary', { + defaultMessage: + 'Failed to validate actions due to the following {errorNum, plural, one {error:} other {# errors:\n-}} {errorList}', + values: { + errorNum: errors.length, + errorList: errors.join('\n- '), + }, + }) + ); + } } diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index 7963ebd885a77..c11dc8be21ca4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -2602,7 +2602,7 @@ describe('create()', () => { }, ]); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Invalid connectors: email connector"` + `"Failed to validate actions due to the following error: Invalid connectors: email connector"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -2760,7 +2760,7 @@ describe('create()', () => { ], }); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default, group2"` + `"Failed to validate actions due to the following error: Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default, group2"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -2790,7 +2790,7 @@ describe('create()', () => { ], }); await expect(rulesClient.create({ data: data2 })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default"` + `"Failed to validate actions due to the following error: Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -2833,7 +2833,7 @@ describe('create()', () => { ], }); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Actions missing frequency parameters: default"` + `"Failed to validate actions due to the following error: Actions missing frequency parameters: default"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -2891,7 +2891,7 @@ describe('create()', () => { ], }); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Actions missing frequency parameters: group2"` + `"Failed to validate actions due to the following error: Actions missing frequency parameters: group2"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -2968,9 +2968,83 @@ describe('create()', () => { ], }); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( - `"Action throttle cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)"` + `"Failed to validate actions due to the following error: Action throttle cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); }); + + test('throws multiple errors when actions have multiple problems', async () => { + rulesClient = new RulesClient({ + ...rulesClientParams, + minimumScheduleInterval: { value: '1m', enforce: true }, + }); + ruleTypeRegistry.get.mockImplementation(() => ({ + id: '123', + name: 'Test', + actionGroups: [ + { id: 'default', name: 'Default' }, + { id: 'group2', name: 'Action Group 2' }, + { id: 'group3', name: 'Action Group 3' }, + ], + recoveryActionGroup: RecoveredActionGroup, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + async executor() { + return { state: {} }; + }, + producer: 'alerts', + useSavedObjectReferences: { + extractReferences: jest.fn(), + injectReferences: jest.fn(), + }, + })); + + const data = getMockData({ + notifyWhen: undefined, + throttle: undefined, + schedule: { interval: '3h' }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onThrottleInterval', + throttle: '1h', + }, + }, + { + group: 'group2', + id: '2', + params: { + foo: true, + }, + frequency: { + summary: false, + notifyWhen: 'onThrottleInterval', + throttle: '3m', + }, + }, + { + group: 'group3', + id: '3', + params: { + foo: true, + }, + }, + ], + }); + await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(` + "Failed to validate actions due to the following 2 errors: + - Actions missing frequency parameters: group3 + - Action throttle cannot be shorter than the schedule interval of 3h: default (1h), group2 (3m)" + `); + expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); + expect(taskManager.schedule).not.toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 0de571c72916b..44c4eeb50fe27 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -1767,7 +1767,7 @@ describe('update()', () => { }, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default, group2"` + `"Failed to validate actions due to the following error: Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default, group2"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -1808,7 +1808,7 @@ describe('update()', () => { }, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default"` + `"Failed to validate actions due to the following error: Cannot specify per-action frequency params when notify_when or throttle are defined at the rule level: default"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -1844,7 +1844,7 @@ describe('update()', () => { }, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Actions missing frequency parameters: default"` + `"Failed to validate actions due to the following error: Actions missing frequency parameters: default"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -1892,7 +1892,7 @@ describe('update()', () => { }, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Actions missing frequency parameters: default"` + `"Failed to validate actions due to the following error: Actions missing frequency parameters: default"` ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); @@ -2016,7 +2016,9 @@ describe('update()', () => { ], }, }) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Invalid connectors: another connector"`); + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to validate actions due to the following error: Invalid connectors: another connector"` + ); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(taskManager.schedule).not.toHaveBeenCalled(); }); 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 dc0c486deeed9..2c3f5b49460fe 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 @@ -38,7 +38,7 @@ describe('service logs', () => { ); }); - it('filter by host names as fallback', () => { + it('does not filter by host names as fallback', () => { expect( getInfrastructureKQLFilter( { @@ -48,9 +48,7 @@ describe('service logs', () => { }, serviceName ) - ).toEqual( - 'service.name: "opbeans-node" or (not service.name and (host.name: "baz" or host.name: "quz"))' - ); + ).toEqual('service.name: "opbeans-node"'); }); }); }); 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 6616eee3d85d1..02cdf96793982 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 @@ -12,11 +12,7 @@ import { useFetcher } from '../../../hooks/use_fetcher'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { APIReturnType } from '../../../services/rest/create_call_apm_api'; -import { - CONTAINER_ID, - HOST_NAME, - SERVICE_NAME, -} from '../../../../common/es_fields/apm'; +import { CONTAINER_ID, SERVICE_NAME } from '../../../../common/es_fields/apm'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useTimeRange } from '../../../hooks/use_time_range'; @@ -70,16 +66,12 @@ export const getInfrastructureKQLFilter = ( | undefined, serviceName: string ) => { - const containerIds = data?.containerIds ?? []; - const hostNames = data?.hostNames ?? []; + const containerIds: string[] = data?.containerIds ?? []; + const containerIdKql = containerIds + .map((id) => `${CONTAINER_ID}: "${id}"`) + .join(' or '); - const infraAttributes = containerIds.length - ? containerIds.map((id) => `${CONTAINER_ID}: "${id}"`) - : hostNames.map((id) => `${HOST_NAME}: "${id}"`); - - const infraAttributesJoined = infraAttributes.join(' or '); - - return infraAttributes.length - ? `${SERVICE_NAME}: "${serviceName}" or (not ${SERVICE_NAME} and (${infraAttributesJoined}))` + return containerIds.length + ? `${SERVICE_NAME}: "${serviceName}" or (not ${SERVICE_NAME} and (${containerIdKql}))` : `${SERVICE_NAME}: "${serviceName}"`; }; diff --git a/x-pack/plugins/apm/public/components/shared/stacktrace/index.tsx b/x-pack/plugins/apm/public/components/shared/stacktrace/index.tsx index eb32a2aa8ce4d..4a3646dbb78c1 100644 --- a/x-pack/plugins/apm/public/components/shared/stacktrace/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/stacktrace/index.tsx @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty, last } from 'lodash'; import React, { Fragment } from 'react'; +import { EuiCodeBlock } from '@elastic/eui'; import { Stackframe } from '../../../../typings/es_schemas/raw/fields/stackframe'; import { EmptyMessage } from '../empty_message'; import { LibraryStacktrace } from './library_stacktrace'; @@ -36,34 +37,36 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { const groups = getGroupedStackframes(stackframes); return ( - - {groups.map((group, i) => { - // library frame - if (group.isLibraryFrame && groups.length > 1) { - return ( - - + + {groups.map((group, i) => { + // library frame + if (group.isLibraryFrame && groups.length > 1) { + return ( + + + + ); + } + + // non-library frame + return group.stackframes.map((stackframe, idx) => ( + + 1} + stackframe={stackframe} /> - ); - } - - // non-library frame - return group.stackframes.map((stackframe, idx) => ( - - 1} - stackframe={stackframe} - /> - - )); - })} - + )); + })} + + ); } diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index ce5c58bcffe35..86907fdd570be 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -225,6 +225,9 @@ export class APMPlugin coreStartPromise: getCoreStart(), plugins: resourcePlugins, config: currentConfig, + }).catch((e) => { + this.logger?.error('Failed to register APM Fleet policy callbacks'); + this.logger?.error(e); }); // This will add an API key to all existing APM package policies @@ -232,6 +235,9 @@ export class APMPlugin coreStartPromise: getCoreStart(), pluginStartPromise: getPluginStart(), logger: this.logger, + }).catch((e) => { + this.logger?.error('Failed to add API keys to APM package policies'); + this.logger?.error(e); }); const taskManager = plugins.taskManager; diff --git a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts index b55e2888c3c7f..75e1aef357088 100644 --- a/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts +++ b/x-pack/plugins/apm/server/routes/alerts/rule_types/transaction_duration/register_transaction_duration_rule_type.ts @@ -59,8 +59,8 @@ import { } from '../get_service_group_fields'; const paramsSchema = schema.object({ - serviceName: schema.string(), - transactionType: schema.string(), + serviceName: schema.maybe(schema.string()), + transactionType: schema.maybe(schema.string()), windowSize: schema.number(), windowUnit: schema.string(), threshold: schema.number(), diff --git a/x-pack/plugins/apm/server/routes/fleet/register_fleet_policy_callbacks.ts b/x-pack/plugins/apm/server/routes/fleet/register_fleet_policy_callbacks.ts index 9057ab065cacb..13815779c6013 100644 --- a/x-pack/plugins/apm/server/routes/fleet/register_fleet_policy_callbacks.ts +++ b/x-pack/plugins/apm/server/routes/fleet/register_fleet_policy_callbacks.ts @@ -75,7 +75,6 @@ function onPackagePolicyDelete({ logger: Logger; }): PostPackagePolicyDeleteCallback { return async (packagePolicies) => { - // console.log(`packagePolicyDelete:`, packagePolicies); const promises = packagePolicies.map(async (packagePolicy) => { if (packagePolicy.package?.name !== 'apm') { return packagePolicy; diff --git a/x-pack/plugins/apm/server/routes/source_maps/create_apm_source_map_index_template.ts b/x-pack/plugins/apm/server/routes/source_maps/create_apm_source_map_index_template.ts index 25a7cf5ae5815..4fa24358e7b07 100644 --- a/x-pack/plugins/apm/server/routes/source_maps/create_apm_source_map_index_template.ts +++ b/x-pack/plugins/apm/server/routes/source_maps/create_apm_source_map_index_template.ts @@ -17,8 +17,9 @@ const indexTemplate: IndicesPutIndexTemplateRequest = { index_patterns: [APM_SOURCE_MAP_INDEX], template: { settings: { - number_of_shards: 1, index: { + number_of_shards: 1, + auto_expand_replicas: '0-2', hidden: true, }, }, diff --git a/x-pack/plugins/banners/server/ui_settings.ts b/x-pack/plugins/banners/server/ui_settings.ts index 7d6f12589d164..fe16b6592dd5f 100644 --- a/x-pack/plugins/banners/server/ui_settings.ts +++ b/x-pack/plugins/banners/server/ui_settings.ts @@ -30,8 +30,7 @@ export const registerSettings = (uiSettings: UiSettingsServiceSetup, config: Ban defaultMessage: 'Banner placement', }), description: i18n.translate('xpack.banners.settings.placement.description', { - defaultMessage: - 'Display a top banner for this space, above the Elastic header. {subscriptionLink}', + defaultMessage: 'Display a top banner above the Elastic header. {subscriptionLink}', values: { subscriptionLink, }, diff --git a/x-pack/plugins/cases/common/api/cases/comment.ts b/x-pack/plugins/cases/common/api/cases/comment.ts index 66204d829b6d4..b768b8f0b593f 100644 --- a/x-pack/plugins/cases/common/api/cases/comment.ts +++ b/x-pack/plugins/cases/common/api/cases/comment.ts @@ -279,6 +279,22 @@ export const FindQueryParamsRt = rt.partial({ export const BulkCreateCommentRequestRt = rt.array(CommentRequestRt); +export const BulkGetAttachmentsRequestRt = rt.type({ + ids: rt.array(rt.string), +}); + +export const BulkGetAttachmentsResponseRt = rt.type({ + attachments: AllCommentsResponseRt, + errors: rt.array( + rt.type({ + error: rt.string, + message: rt.string, + status: rt.union([rt.undefined, rt.number]), + attachmentId: rt.string, + }) + ), +}); + export type FindQueryParams = rt.TypeOf; export type AttributesTypeActions = rt.TypeOf; export type AttributesTypeAlerts = rt.TypeOf; @@ -317,3 +333,5 @@ export type CommentRequestExternalReferenceType = rt.TypeOf; export type CommentRequestExternalReferenceNoSOType = rt.TypeOf; export type CommentRequestPersistableStateType = rt.TypeOf; +export type BulkGetAttachmentsResponse = rt.TypeOf; +export type BulkGetAttachmentsRequest = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/response.ts b/x-pack/plugins/cases/common/api/cases/user_actions/response.ts index b36cc7104001c..8f0ef5517db4f 100644 --- a/x-pack/plugins/cases/common/api/cases/user_actions/response.ts +++ b/x-pack/plugins/cases/common/api/cases/user_actions/response.ts @@ -8,16 +8,17 @@ import * as rt from 'io-ts'; import type { ActionsRt, ActionTypeValues } from './common'; + import { CaseUserActionInjectedIdsRt, CaseUserActionInjectedDeprecatedIdsRt, UserActionCommonAttributesRt, } from './common'; -import { CreateCaseUserActionRt } from './create_case'; +import { CreateCaseUserActionRt, CreateCaseUserActionWithoutConnectorIdRt } from './create_case'; import { DescriptionUserActionRt } from './description'; import { CommentUserActionRt } from './comment'; -import { ConnectorUserActionRt } from './connector'; -import { PushedUserActionRt } from './pushed'; +import { ConnectorUserActionRt, ConnectorUserActionWithoutConnectorIdRt } from './connector'; +import { PushedUserActionRt, PushedUserActionWithoutConnectorIdRt } from './pushed'; import { TagsUserActionRt } from './tags'; import { TitleUserActionRt } from './title'; import { SettingsUserActionRt } from './settings'; @@ -25,6 +26,7 @@ import { StatusUserActionRt } from './status'; import { DeleteCaseUserActionRt } from './delete_case'; import { SeverityUserActionRt } from './severity'; import { AssigneesUserActionRt } from './assignees'; +import { CaseUserActionStatsRt } from './stats'; const CommonUserActionsRt = rt.union([ DescriptionUserActionRt, @@ -45,11 +47,11 @@ export const UserActionsRt = rt.union([ DeleteCaseUserActionRt, ]); -export const UserActionsWithoutConnectorIdRt = rt.union([ +const UserActionsWithoutConnectorIdRt = rt.union([ CommonUserActionsRt, - CreateCaseUserActionRt, - ConnectorUserActionRt, - PushedUserActionRt, + CreateCaseUserActionWithoutConnectorIdRt, + ConnectorUserActionWithoutConnectorIdRt, + PushedUserActionWithoutConnectorIdRt, DeleteCaseUserActionRt, ]); @@ -80,14 +82,16 @@ const CaseUserActionResponseRt = rt.intersection([ }), ]); -export const CaseUserActionAttributesRt = CaseUserActionBasicRt; +const CaseUserActionAttributesRt = CaseUserActionBasicRt; export const CaseUserActionsResponseRt = rt.array(CaseUserActionResponseRt); export const CaseUserActionsDeprecatedResponseRt = rt.array(CaseUserActionDeprecatedResponseRt); +export const CaseUserActionStatsResponseRt = CaseUserActionStatsRt; export type CaseUserActionAttributes = rt.TypeOf; export type CaseUserActionAttributesWithoutConnectorId = rt.TypeOf< - typeof CaseUserActionAttributesRt + typeof CaseUserActionBasicWithoutConnectorIdRt >; +export type CaseUserActionStatsResponse = rt.TypeOf; export type CaseUserActionsResponse = rt.TypeOf; export type CaseUserActionResponse = rt.TypeOf; export type CaseUserActionsDeprecatedResponse = rt.TypeOf< @@ -100,6 +104,3 @@ export type UserAction = rt.TypeOf; export type UserActionTypes = ActionTypeValues; export type CaseUserAction = rt.TypeOf; -export type CaseUserActionWithoutConnectorId = rt.TypeOf< - typeof CaseUserActionBasicWithoutConnectorIdRt ->; diff --git a/x-pack/plugins/cases/common/api/cases/user_actions/stats.ts b/x-pack/plugins/cases/common/api/cases/user_actions/stats.ts new file mode 100644 index 0000000000000..de0a6439e0bdb --- /dev/null +++ b/x-pack/plugins/cases/common/api/cases/user_actions/stats.ts @@ -0,0 +1,16 @@ +/* + * 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 * as rt from 'io-ts'; + +export const CaseUserActionStatsRt = rt.type({ + total: rt.number, + total_comments: rt.number, + total_other_actions: rt.number, +}); + +export type CaseUserActionStats = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/connectors/connector.ts b/x-pack/plugins/cases/common/api/connectors/connector.ts index f3c733c85cd8c..7d96593d01316 100644 --- a/x-pack/plugins/cases/common/api/connectors/connector.ts +++ b/x-pack/plugins/cases/common/api/connectors/connector.ts @@ -18,14 +18,6 @@ import { SwimlaneFieldsRT } from './swimlane'; export type ActionConnector = ActionResult; export type ActionTypeConnector = ActionType; -export const ConnectorFieldsRt = rt.union([ - JiraFieldsRT, - ResilientFieldsRT, - ServiceNowITSMFieldsRT, - ServiceNowSIRFieldsRT, - rt.null, -]); - export enum ConnectorTypes { casesWebhook = '.cases-webhook', jira = '.jira', @@ -114,6 +106,3 @@ export type ConnectorServiceNowITSMTypeFields = rt.TypeOf< typeof ConnectorServiceNowITSMTypeFieldsRt >; export type ConnectorServiceNowSIRTypeFields = rt.TypeOf; - -// we need to change these types back and forth for storing in ES (arrays overwrite, objects merge) -export type ConnectorFields = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/connectors/get_connectors.ts b/x-pack/plugins/cases/common/api/connectors/get_connectors.ts index 5c29d585287da..8342c270b160e 100644 --- a/x-pack/plugins/cases/common/api/connectors/get_connectors.ts +++ b/x-pack/plugins/cases/common/api/connectors/get_connectors.ts @@ -9,15 +9,19 @@ import * as rt from 'io-ts'; import { CaseConnectorRt } from './connector'; import { CaseExternalServiceBasicRt } from '../cases'; +const PushDetailsRt = rt.type({ + latestUserActionPushDate: rt.string, + oldestUserActionPushDate: rt.string, + externalService: CaseExternalServiceBasicRt, +}); + const CaseConnectorPushInfoRt = rt.intersection([ rt.type({ needsToBePushed: rt.boolean, hasBeenPushed: rt.boolean, }), rt.partial({ - latestUserActionPushDate: rt.string, - oldestUserActionPushDate: rt.string, - externalService: CaseExternalServiceBasicRt, + details: PushDetailsRt, }), ]); @@ -32,3 +36,4 @@ export const GetCaseConnectorsResponseRt = rt.record( ); export type GetCaseConnectorsResponse = rt.TypeOf; +export type GetCaseConnectorsPushDetails = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/helpers.ts b/x-pack/plugins/cases/common/api/helpers.ts index 2f2fe14a4f991..746f2bd6972ab 100644 --- a/x-pack/plugins/cases/common/api/helpers.ts +++ b/x-pack/plugins/cases/common/api/helpers.ts @@ -16,6 +16,9 @@ import { CASE_ALERTS_URL, CASE_COMMENT_DELETE_URL, CASE_FIND_USER_ACTIONS_URL, + INTERNAL_GET_CASE_USER_ACTIONS_STATS_URL, + INTERNAL_BULK_GET_ATTACHMENTS_URL, + INTERNAL_CONNECTORS_URL, } from '../constants'; export const getCaseDetailsUrl = (id: string): string => { @@ -42,6 +45,10 @@ export const getCaseUserActionUrl = (id: string): string => { return CASE_USER_ACTIONS_URL.replace('{case_id}', id); }; +export const getCaseUserActionStatsUrl = (id: string): string => { + return INTERNAL_GET_CASE_USER_ACTIONS_STATS_URL.replace('{case_id}', id); +}; + export const getCaseFindUserActionsUrl = (id: string): string => { return CASE_FIND_USER_ACTIONS_URL.replace('{case_id}', id); }; @@ -57,3 +64,11 @@ export const getCaseConfigurationDetailsUrl = (configureID: string): string => { export const getCasesFromAlertsUrl = (alertId: string): string => { return CASE_ALERTS_URL.replace('{alert_id}', alertId); }; + +export const getCaseBulkGetAttachmentsUrl = (id: string): string => { + return INTERNAL_BULK_GET_ATTACHMENTS_URL.replace('{case_id}', id); +}; + +export const getCaseConnectorsUrl = (id: string): string => { + return INTERNAL_CONNECTORS_URL.replace('{case_id}', id); +}; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index c1e65087224cf..c359353f80b80 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -88,10 +88,14 @@ export const CASE_METRICS_DETAILS_URL = `${CASES_URL}/metrics/{case_id}` as cons export const CASES_INTERNAL_URL = '/internal/cases' as const; export const INTERNAL_BULK_CREATE_ATTACHMENTS_URL = `${CASES_INTERNAL_URL}/{case_id}/attachments/_bulk_create` as const; +export const INTERNAL_BULK_GET_ATTACHMENTS_URL = + `${CASES_INTERNAL_URL}/{case_id}/attachments/_bulk_get` as const; export const INTERNAL_SUGGEST_USER_PROFILES_URL = `${CASES_INTERNAL_URL}/_suggest_user_profiles` as const; export const INTERNAL_CONNECTORS_URL = `${CASES_INTERNAL_URL}/{case_id}/_connectors` as const; export const INTERNAL_BULK_GET_CASES_URL = `${CASES_INTERNAL_URL}/_bulk_get` as const; +export const INTERNAL_GET_CASE_USER_ACTIONS_STATS_URL = + `${CASES_INTERNAL_URL}/{case_id}/user_actions/_stats` as const; /** * Action routes @@ -138,6 +142,7 @@ export const OWNER_INFO = { * Searching */ export const MAX_DOCS_PER_PAGE = 10000 as const; +export const MAX_BULK_GET_ATTACHMENTS = MAX_DOCS_PER_PAGE; export const MAX_CONCURRENT_SEARCHES = 10 as const; export const MAX_BULK_GET_CASES = 1000 as const; diff --git a/x-pack/plugins/cases/common/ui/types.ts b/x-pack/plugins/cases/common/ui/types.ts index 8231dd945bf9e..f4df0c1b72f98 100644 --- a/x-pack/plugins/cases/common/ui/types.ts +++ b/x-pack/plugins/cases/common/ui/types.ts @@ -17,7 +17,6 @@ import type { CaseStatuses, User, ActionConnector, - CaseExternalServiceBasic, CaseUserActionResponse, SingleCaseMetricsResponse, CommentResponse, @@ -30,6 +29,7 @@ import type { CaseSeverity, CommentResponseExternalReferenceType, CommentResponseTypePersistableState, + GetCaseConnectorsResponse, } from '../api'; import type { PUSH_CASES_CAPABILITY } from '../constants'; import type { SnakeToCamelCase } from '../types'; @@ -81,12 +81,12 @@ export type CaseUserActions = SnakeToCamelCase; export type FindCaseUserActions = Omit, 'userActions'> & { userActions: CaseUserActions[]; }; -export type CaseExternalService = SnakeToCamelCase; export type Case = Omit, 'comments'> & { comments: Comment[] }; export type Cases = Omit, 'cases'> & { cases: Case[] }; export type CasesStatus = SnakeToCamelCase; export type CasesMetrics = SnakeToCamelCase; export type CaseUpdateRequest = SnakeToCamelCase; +export type CaseConnectors = SnakeToCamelCase; export interface ResolvedCase { case: Case; diff --git a/x-pack/plugins/cases/docs/openapi/bundled.json b/x-pack/plugins/cases/docs/openapi/bundled.json index 7260061d76b0b..37647324ccf7a 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.json +++ b/x-pack/plugins/cases/docs/openapi/bundled.json @@ -2111,9 +2111,9 @@ "description": "The page number to return.", "schema": { "type": "string", - "default": 1 + "default": "1" }, - "example": 1 + "example": "1" }, { "name": "perPage", @@ -2121,9 +2121,9 @@ "description": "The number of user actions to return per page.", "schema": { "type": "string", - "default": 20 + "default": "20" }, - "example": 20 + "example": "20" }, { "name": "sortOrder", @@ -2245,6 +2245,7 @@ }, "in": "header", "name": "kbn-xsrf", + "description": "Cross-site request forgery protection", "required": true }, "space_id": { diff --git a/x-pack/plugins/cases/docs/openapi/bundled.yaml b/x-pack/plugins/cases/docs/openapi/bundled.yaml index aa55e180191cb..8098a2d8787ff 100644 --- a/x-pack/plugins/cases/docs/openapi/bundled.yaml +++ b/x-pack/plugins/cases/docs/openapi/bundled.yaml @@ -1309,15 +1309,15 @@ paths: description: The page number to return. schema: type: string - default: 1 - example: 1 + default: '1' + example: '1' - name: perPage in: query description: The number of user actions to return per page. schema: type: string - default: 20 - example: 20 + default: '20' + example: '20' - name: sortOrder in: query description: Determines the sort order. @@ -1398,6 +1398,7 @@ components: type: string in: header name: kbn-xsrf + description: Cross-site request forgery protection required: true space_id: in: path diff --git a/x-pack/plugins/cases/docs/openapi/components/headers/kbn_xsrf.yaml b/x-pack/plugins/cases/docs/openapi/components/headers/kbn_xsrf.yaml index 3d8dfae634e68..fe0402a43aa03 100644 --- a/x-pack/plugins/cases/docs/openapi/components/headers/kbn_xsrf.yaml +++ b/x-pack/plugins/cases/docs/openapi/components/headers/kbn_xsrf.yaml @@ -2,4 +2,5 @@ schema: type: string in: header name: kbn-xsrf +description: Cross-site request forgery protection required: true diff --git a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml index 8ec83b50416ef..8cdeca5b9a7a9 100644 --- a/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml +++ b/x-pack/plugins/cases/docs/openapi/paths/s@{spaceid}@api@cases@{caseid}@user_actions@_find.yaml @@ -15,15 +15,15 @@ get: description: The page number to return. schema: type: string - default: 1 - example: 1 + default: "1" + example: "1" - name: perPage in: query description: The number of user actions to return per page. schema: type: string - default: 20 - example: 20 + default: "20" + example: "20" - name: sortOrder in: query description: Determines the sort order. diff --git a/x-pack/plugins/cases/public/common/mock/connectors.ts b/x-pack/plugins/cases/public/common/mock/connectors.ts index e0b7b26a4c8a4..a036b5a6b8981 100644 --- a/x-pack/plugins/cases/public/common/mock/connectors.ts +++ b/x-pack/plugins/cases/public/common/mock/connectors.ts @@ -5,13 +5,16 @@ * 2.0. */ +import { set } from 'lodash'; import type { ActionConnector, ActionTypeConnector } from '../../../common/api'; +import { basicPush } from '../../containers/mock'; +import type { CaseConnectors } from '../../containers/types'; export const connectorsMock: ActionConnector[] = [ { id: 'servicenow-1', actionTypeId: '.servicenow', - name: 'My Connector', + name: 'My SN connector', config: { apiUrl: 'https://instance1.service-now.com', }, @@ -21,7 +24,7 @@ export const connectorsMock: ActionConnector[] = [ { id: 'resilient-2', actionTypeId: '.resilient', - name: 'My Connector 2', + name: 'My Resilient connector', config: { apiUrl: 'https://test/', orgId: '201', @@ -52,7 +55,7 @@ export const connectorsMock: ActionConnector[] = [ { id: 'servicenow-uses-table-api', actionTypeId: '.servicenow', - name: 'My Connector', + name: 'My deprecated SN connector', config: { apiUrl: 'https://instance1.service-now.com', usesTableApi: true, @@ -118,3 +121,52 @@ export const actionTypesMock: ActionTypeConnector[] = [ supportedFeatureIds: ['alerting', 'cases'], }, ]; + +/** + * Construct a mock getConnectors response object + * + * @param overrides is an object where the key is the path for setting a field in the returned object. For example to set + * the externalService.connectorId pass the following overrides object: + * + * ``` + * { + * 'push.details.externalService.connectorId': '123' + * } + * ``` + */ +export const getCaseConnectorsMockResponse = ( + overrides?: Record +): CaseConnectors => { + return connectorsMock.reduce((acc, connector) => { + const newConnectors: CaseConnectors = { + ...acc, + [connector.id]: { + id: connector.id, + name: connector.name, + type: connector.actionTypeId, + fields: null, + push: { + needsToBePushed: false, + hasBeenPushed: true, + details: { + oldestUserActionPushDate: '2023-01-17T09:46:29.813Z', + latestUserActionPushDate: '2023-01-17T09:46:29.813Z', + externalService: { + ...basicPush, + connectorId: connector.id, + connectorName: connector.name, + }, + }, + }, + }, + }; + + if (overrides != null) { + for (const path of Object.keys(overrides)) { + set(newConnectors[connector.id], path, overrides[path]); + } + } + + return newConnectors; + }, {}); +}; diff --git a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx index 65156c39202b4..909bb1dd24ea0 100644 --- a/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/all_cases_list.test.tsx @@ -36,7 +36,7 @@ import { registerConnectorsToMockActionRegistry } from '../../common/mock/regist import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock'; import { waitForComponentToUpdate } from '../../common/test_utils'; import { useCreateAttachments } from '../../containers/use_create_attachments'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; import { useUpdateCase } from '../../containers/use_update_case'; import { useGetCases, DEFAULT_QUERY_PARAMS } from '../../containers/use_get_cases'; @@ -52,7 +52,7 @@ jest.mock('../../containers/use_get_action_license'); jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/user_profiles/use_get_current_user_profile'); jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../common/lib/kibana'); jest.mock('../../common/navigation/hooks'); jest.mock('../app/use_available_owners', () => ({ @@ -66,7 +66,7 @@ const useGetTagsMock = useGetTags as jest.Mock; const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock; const useKibanaMock = useKibana as jest.MockedFunction; -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useCreateAttachmentsMock = useCreateAttachments as jest.Mock; const useUpdateCaseMock = useUpdateCase as jest.Mock; const useLicenseMock = useLicense as jest.Mock; 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 3b2b510a26f82..ab50f16083f8b 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 @@ -23,7 +23,7 @@ import type { EuiBasicTableOnChange } from './types'; import { CasesTable } from './table'; import { useCasesContext } from '../cases_context/use_cases_context'; import { CasesMetrics } from './cases_metrics'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { initialData, useGetCases } from '../../containers/use_get_cases'; import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; @@ -98,7 +98,7 @@ export const AllCasesList = React.memo( const { data: currentUserProfile, isLoading: isLoadingCurrentUserProfile } = useGetCurrentUserProfile(); - const { data: connectors = [] } = useGetConnectors(); + const { data: connectors = [] } = useGetSupportedActionConnectors(); const sorting = useMemo( () => ({ diff --git a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx index 72cdbc0c0ed5b..7ce32a2f123a5 100644 --- a/x-pack/plugins/cases/public/components/all_cases/index.test.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/index.test.tsx @@ -13,7 +13,7 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer, noCreateCasesPermissions } from '../../common/mock'; import { useGetActionLicense } from '../../containers/use_get_action_license'; import { connectorsMock, useGetCasesMockState } from '../../containers/mock'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; import { useGetCases } from '../../containers/use_get_cases'; import { useGetCurrentUserProfile } from '../../containers/user_profiles/use_get_current_user_profile'; @@ -26,14 +26,14 @@ jest.mock('../../containers/use_get_action_license', () => { useGetActionLicense: jest.fn(), }; }); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/api'); jest.mock('../../containers/use_get_cases'); jest.mock('../../containers/user_profiles/use_get_current_user_profile'); jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles'); jest.mock('../../api'); -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useGetCasesMock = useGetCases as jest.Mock; const useGetActionLicenseMock = useGetActionLicense as jest.Mock; const useGetCurrentUserProfileMock = useGetCurrentUserProfile as jest.Mock; diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx index 16bef4b933d6e..195d02f7931cf 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.test.tsx @@ -29,10 +29,6 @@ jest.mock('react-router-dom', () => { }); const defaultProps = { - allCasesNavigation: { - href: 'all-cases-href', - onClick: () => {}, - }, caseData: basicCase, currentExternalIncident: null, }; @@ -123,10 +119,6 @@ describe('CaseView actions', () => { {...defaultProps} currentExternalIncident={{ ...basicPush, - firstPushIndex: 5, - lastPushIndex: 5, - commentsToUpdate: [], - hasDataToPush: false, }} /> diff --git a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx index b0697f9c962e7..d464946b60f82 100644 --- a/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx +++ b/x-pack/plugins/cases/public/components/case_action_bar/actions.tsx @@ -13,14 +13,13 @@ import { useDeleteCases } from '../../containers/use_delete_cases'; import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { PropertyActions } from '../property_actions'; import type { Case } from '../../../common/ui/types'; -import type { CaseService } from '../../containers/use_find_case_user_actions'; import { useAllCasesNavigation } from '../../common/navigation'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesToast } from '../../common/use_cases_toast'; interface CaseViewActions { caseData: Case; - currentExternalIncident: CaseService | null; + currentExternalIncident: Case['externalService']; } const ActionsComponent: React.FC = ({ caseData, currentExternalIncident }) => { @@ -48,22 +47,22 @@ const ActionsComponent: React.FC = ({ caseData, currentExternal showSuccessToast(i18n.COPY_ID_ACTION_SUCCESS); }, }, - ...(permissions.delete + ...(currentExternalIncident != null && !isEmpty(currentExternalIncident?.externalUrl) ? [ { - iconType: 'trash', - label: i18n.DELETE_CASE(), - color: 'danger' as const, - onClick: openModal, + iconType: 'popout', + label: i18n.VIEW_INCIDENT(currentExternalIncident?.externalTitle ?? ''), + onClick: () => window.open(currentExternalIncident?.externalUrl, '_blank'), }, ] : []), - ...(currentExternalIncident != null && !isEmpty(currentExternalIncident?.externalUrl) + ...(permissions.delete ? [ { - iconType: 'popout', - label: i18n.VIEW_INCIDENT(currentExternalIncident?.externalTitle ?? ''), - onClick: () => window.open(currentExternalIncident?.externalUrl, '_blank'), + iconType: 'trash', + label: i18n.DELETE_CASE(), + color: 'danger' as const, + onClick: openModal, }, ] : []), 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 81560f99e5427..7263514fb6baa 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 @@ -7,10 +7,10 @@ import React from 'react'; import { mount } from 'enzyme'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { basicCase, caseUserActions, getAlertUserAction } from '../../containers/mock'; +import { basicCase } from '../../containers/mock'; import type { CaseActionBarProps } from '.'; import { CaseActionBar } from '.'; import { @@ -19,42 +19,31 @@ import { noUpdateCasesPermissions, TestProviders, } from '../../common/mock'; -import { useFindCaseUserActions } from '../../containers/use_find_case_user_actions'; +import { useGetCaseConnectors } from '../../containers/use_get_case_connectors'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; -jest.mock('../../containers/use_find_case_user_actions'); +jest.mock('../../containers/use_get_case_connectors'); jest.mock('../case_view/use_on_refresh_case_view_page'); -const useFindCaseUserActionsMock = useFindCaseUserActions as jest.Mock; -const defaultUseFindCaseUserActions = { - data: { - caseUserActions: [...caseUserActions, getAlertUserAction()], - caseServices: {}, - hasDataToPush: false, - participants: [basicCase.createdBy], - }, - isLoading: false, - isError: false, -}; +const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock; describe('CaseActionBar', () => { + const caseConnectors = getCaseConnectorsMockResponse(); + const onUpdateField = jest.fn(); - const defaultProps = { - allCasesNavigation: { - href: 'all-cases-href', - onClick: () => {}, - }, + const defaultProps: CaseActionBarProps = { caseData: basicCase, - disableAlerting: false, isLoading: false, onUpdateField, - currentExternalIncident: null, - metricsFeatures: [], }; beforeEach(() => { jest.clearAllMocks(); - useFindCaseUserActionsMock.mockReturnValue(defaultUseFindCaseUserActions); + useGetCaseConnectorsMock.mockReturnValue({ + isLoading: false, + data: caseConnectors, + }); }); it('renders', () => { @@ -70,21 +59,6 @@ describe('CaseActionBar', () => { expect(wrapper.find(`[data-test-subj="sync-alerts-switch"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="case-refresh"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="case-view-actions"]`).exists()).toBeTruthy(); - // no loading bar - expect(wrapper.find(`[data-test-subj="case-view-action-bar-spinner"]`).exists()).toBeFalsy(); - }); - - it('shows a loading bar when user actions are loaded', async () => { - useFindCaseUserActionsMock.mockReturnValue({ - data: undefined, - isLoading: true, - }); - const wrapper = mount( - - - - ); - expect(wrapper.find(`[data-test-subj="case-view-action-bar-spinner"]`).exists()).toBeTruthy(); }); it('should show correct status', () => { @@ -249,4 +223,38 @@ describe('CaseActionBar', () => { userEvent.click(screen.getByTestId('property-actions-ellipses')); expect(queryByText('Delete case')).toBeInTheDocument(); }); + + it('shows the external incident action', async () => { + const connector = caseConnectors['servicenow-1']; + const { push, ...connectorWithoutPush } = connector; + + const props = { + ...defaultProps, + caseData: { ...defaultProps.caseData, connector: connectorWithoutPush }, + }; + + render( + + + + ); + + userEvent.click(screen.getByTestId('property-actions-ellipses')); + + await waitFor(() => { + expect(screen.getByTestId('property-actions-popout')).toBeInTheDocument(); + }); + }); + + it('does not show the external incident action', async () => { + render( + + + + ); + + userEvent.click(screen.getByTestId('property-actions-ellipses')); + + expect(screen.queryByTestId('property-actions-popout')).not.toBeInTheDocument(); + }); }); 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 d21427d579893..b8be843e8c659 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import styled, { css } from 'styled-components'; import { EuiButtonEmpty, @@ -15,13 +15,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiIconTip, - EuiLoadingSpinner, } from '@elastic/eui'; import type { Case } from '../../../common/ui/types'; import type { CaseStatuses } from '../../../common/api'; import * as i18n from '../case_view/translations'; import { Actions } from './actions'; -import { useFindCaseUserActions } from '../../containers/use_find_case_user_actions'; import { StatusContextMenu } from './status_context_menu'; import { SyncAlertsSwitch } from '../case_settings/sync_alerts_switch'; import type { OnUpdateFields } from '../case_view/types'; @@ -30,6 +28,7 @@ import { getStatusDate, getStatusTitle } from './helpers'; import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesFeatures } from '../../common/use_cases_features'; +import { useGetCaseConnectors } from '../../containers/use_get_case_connectors'; const MyDescriptionList = styled(EuiDescriptionList)` ${({ theme }) => css` @@ -56,9 +55,14 @@ const CaseActionBarComponent: React.FC = ({ }) => { const { permissions } = useCasesContext(); const { isSyncAlertsEnabled, metricsFeatures } = useCasesFeatures(); - const date = useMemo(() => getStatusDate(caseData), [caseData]); - const title = useMemo(() => getStatusTitle(caseData.status), [caseData.status]); + + const { data: caseConnectors } = useGetCaseConnectors(caseData.id); + + const date = getStatusDate(caseData); + const title = getStatusTitle(caseData.status); + const refreshCaseViewPage = useRefreshCaseViewPage(); + const onStatusChanged = useCallback( (status: CaseStatuses) => onUpdateField({ @@ -68,19 +72,8 @@ const CaseActionBarComponent: React.FC = ({ [onUpdateField] ); - const { data: userActionsData, isLoading: isLoadingUserActions } = useFindCaseUserActions( - caseData.id, - caseData.connector.id - ); - - const currentExternalIncident = useMemo( - () => - userActionsData?.caseServices != null && - userActionsData.caseServices[caseData.connector.id] != null - ? userActionsData.caseServices[caseData.connector.id] - : null, - [userActionsData?.caseServices, caseData.connector] - ); + const currentExternalIncident = + caseConnectors?.[caseData.connector.id]?.push.details?.externalService ?? null; const onSyncAlertsChanged = useCallback( (syncAlerts: boolean) => @@ -93,92 +86,84 @@ const CaseActionBarComponent: React.FC = ({ return ( - {isLoadingUserActions ? ( - - - - ) : ( - <> - - - - - {i18n.STATUS} - - - - - {!metricsFeatures.includes('lifespan') ? ( - - {title} - - - - - ) : null} - - - - - - - {permissions.update && isSyncAlertsEnabled && ( - - - - - {i18n.SYNC_ALERTS} - - - - - - - - - - - )} - - - - {i18n.CASE_REFRESH} - - - - - - - - - )} + + + + + {i18n.STATUS} + + + + + {!metricsFeatures.includes('lifespan') ? ( + + {title} + + + + + ) : null} + + + + + + + {permissions.update && isSyncAlertsEnabled && ( + + + + + {i18n.SYNC_ALERTS} + + + + + + + + + + + )} + + + + {i18n.CASE_REFRESH} + + + + + + + ); }; diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx index 1dc62eeba4d97..6c5814c7830f9 100644 --- a/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.test.tsx @@ -14,7 +14,7 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import '../../common/mock/match_media'; import { useCaseViewNavigation, useUrlParams } from '../../common/navigation/hooks'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { basicCaseClosed, connectorsMock } from '../../containers/mock'; import type { UseGetCase } from '../../containers/use_get_case'; import { useGetCase } from '../../containers/use_get_case'; @@ -22,6 +22,7 @@ import { useGetCaseMetrics } from '../../containers/use_get_case_metrics'; import { useFindCaseUserActions } from '../../containers/use_find_case_user_actions'; import { useGetTags } from '../../containers/use_get_tags'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; +import { useGetCaseConnectors } from '../../containers/use_get_case_connectors'; import { useUpdateCase } from '../../containers/use_update_case'; import { useBulkGetUserProfiles } from '../../containers/user_profiles/use_bulk_get_user_profiles'; import { CaseViewPage } from './case_view_page'; @@ -37,6 +38,7 @@ import type { CaseViewPageProps } from './types'; import { userProfiles } from '../../containers/user_profiles/api.mock'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { CASE_VIEW_PAGE_TABS } from '../../../common/types'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; jest.mock('../../containers/use_get_action_license'); jest.mock('../../containers/use_update_case'); @@ -44,8 +46,9 @@ jest.mock('../../containers/use_get_case_metrics'); jest.mock('../../containers/use_find_case_user_actions'); jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/use_get_case'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/use_post_push_to_service'); +jest.mock('../../containers/use_get_case_connectors'); jest.mock('../../containers/user_profiles/use_bulk_get_user_profiles'); jest.mock('../user_actions/timestamp', () => ({ UserActionTimestamp: () => <>, @@ -59,8 +62,9 @@ const useUrlParamsMock = useUrlParams as jest.Mock; const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock; const useUpdateCaseMock = useUpdateCase as jest.Mock; const useFindCaseUserActionsMock = useFindCaseUserActions as jest.Mock; -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const usePostPushToServiceMock = usePostPushToService as jest.Mock; +const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock; const useGetCaseMetricsMock = useGetCaseMetrics as jest.Mock; const useGetTagsMock = useGetTags as jest.Mock; const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock; @@ -94,6 +98,7 @@ describe('CaseViewPage', () => { const pushCaseToExternalService = jest.fn(); const data = caseProps.caseData; let appMockRenderer: AppMockRenderer; + const caseConnectors = getCaseConnectorsMockResponse(); beforeEach(() => { mockGetCase(); @@ -102,6 +107,10 @@ describe('CaseViewPage', () => { useGetCaseMetricsMock.mockReturnValue(defaultGetCaseMetrics); useFindCaseUserActionsMock.mockReturnValue(defaultUseFindCaseUserActions); usePostPushToServiceMock.mockReturnValue({ isLoading: false, pushCaseToExternalService }); + useGetCaseConnectorsMock.mockReturnValue({ + isLoading: false, + data: caseConnectors, + }); useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false }); useGetTagsMock.mockReturnValue({ data: [], isLoading: false }); useBulkGetUserProfilesMock.mockReturnValue({ data: new Map(), isLoading: false }); @@ -249,17 +258,20 @@ describe('CaseViewPage', () => { }); it('should push updates on button click', async () => { - useFindCaseUserActionsMock.mockImplementation(() => ({ - ...defaultUseFindCaseUserActions, + useGetCaseConnectorsMock.mockImplementation(() => ({ + isLoading: false, data: { - ...defaultUseFindCaseUserActions.data, - hasDataToPush: true, + ...caseConnectors, + 'resilient-2': { + ...caseConnectors['resilient-2'], + push: { ...caseConnectors['resilient-2'].push, needsToBePushed: true }, + }, }, })); const result = appMockRenderer.render(); - expect(result.getByTestId('has-data-to-push-button')).toBeInTheDocument(); + expect(result.getByTestId('push-to-external-service')).toBeInTheDocument(); userEvent.click(result.getByTestId('push-to-external-service')); @@ -314,7 +326,7 @@ describe('CaseViewPage', () => { expect(updateObject.updateKey).toEqual('connector'); expect(updateObject.updateValue).toEqual({ id: 'resilient-2', - name: 'My Connector 2', + name: 'My Resilient connector', type: ConnectorTypes.resilient, fields: { incidentTypes: null, @@ -391,20 +403,15 @@ describe('CaseViewPage', () => { it('should show the correct connector name on the push button', async () => { useGetConnectorsMock.mockImplementation(() => ({ data: connectorsMock, isLoading: false })); - useFindCaseUserActionsMock.mockImplementation(() => ({ - ...defaultUseFindCaseUserActions, - data: { - ...defaultUseFindCaseUserActions.data, - hasDataToPush: true, - }, - })); const result = appMockRenderer.render( ); await waitFor(() => { - expect(result.getByTestId('has-data-to-push-button')).toHaveTextContent('My Connector 2'); + expect(result.getByTestId('push-to-external-service')).toHaveTextContent( + 'My Resilient connector' + ); }); }); diff --git a/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx b/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx index af796ff796d85..6e7e7348a8ab4 100644 --- a/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx +++ b/x-pack/plugins/cases/public/components/case_view/case_view_page.tsx @@ -59,7 +59,6 @@ export const CaseViewPage = React.memo( const timelineUi = useTimelineContext()?.ui; const { onUpdateField, isLoading, loadingKey } = useOnUpdateField({ - caseId, caseData, }); diff --git a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx index bc6b1d7c0dc81..941a983659183 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_activity.test.tsx @@ -21,14 +21,17 @@ import type { Case } from '../../../../common'; import type { CaseViewProps } from '../types'; import { useFindCaseUserActions } from '../../../containers/use_find_case_user_actions'; import { usePostPushToService } from '../../../containers/use_post_push_to_service'; -import { useGetConnectors } from '../../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../../containers/use_get_tags'; import { useBulkGetUserProfiles } from '../../../containers/user_profiles/use_bulk_get_user_profiles'; +import { useGetCaseConnectors } from '../../../containers/use_get_case_connectors'; import { licensingMock } from '@kbn/licensing-plugin/public/mocks'; import { waitForComponentToUpdate } from '../../../common/test_utils'; +import { waitFor } from '@testing-library/dom'; +import { getCaseConnectorsMockResponse } from '../../../common/mock/connectors'; jest.mock('../../../containers/use_find_case_user_actions'); -jest.mock('../../../containers/configure/use_connectors'); +jest.mock('../../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../../containers/use_post_push_to_service'); jest.mock('../../user_actions/timestamp', () => ({ UserActionTimestamp: () => <>, @@ -37,6 +40,7 @@ jest.mock('../../../common/navigation/hooks'); jest.mock('../../../containers/use_get_action_license'); jest.mock('../../../containers/use_get_tags'); jest.mock('../../../containers/user_profiles/use_bulk_get_user_profiles'); +jest.mock('../../../containers/use_get_case_connectors'); (useGetTags as jest.Mock).mockReturnValue({ data: ['coke', 'pepsi'], refetch: jest.fn() }); @@ -76,8 +80,6 @@ const pushCaseToExternalService = jest.fn(); const defaultUseFindCaseUserActions = { data: { caseUserActions: [...caseUserActions, getAlertUserAction()], - caseServices: {}, - hasDataToPush: false, participants: [caseData.createdBy], }, refetch: fetchCaseUserActions, @@ -92,16 +94,23 @@ export const caseProps = { }; const useFindCaseUserActionsMock = useFindCaseUserActions as jest.Mock; -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const usePostPushToServiceMock = usePostPushToService as jest.Mock; const useBulkGetUserProfilesMock = useBulkGetUserProfiles as jest.Mock; +const useGetCaseConnectorsMock = useGetCaseConnectors as jest.Mock; describe('Case View Page activity tab', () => { + const caseConnectors = getCaseConnectorsMockResponse(); + beforeAll(() => { useFindCaseUserActionsMock.mockReturnValue(defaultUseFindCaseUserActions); useGetConnectorsMock.mockReturnValue({ data: connectorsMock, isLoading: false }); usePostPushToServiceMock.mockReturnValue({ isLoading: false, pushCaseToExternalService }); useBulkGetUserProfilesMock.mockReturnValue({ isLoading: false, data: new Map() }); + useGetCaseConnectorsMock.mockReturnValue({ + isLoading: false, + data: caseConnectors, + }); }); let appMockRender: AppMockRenderer; @@ -126,7 +135,7 @@ describe('Case View Page activity tab', () => { expect(result.getByTestId('case-tags')).toBeTruthy(); expect(result.getByTestId('connector-edit-header')).toBeTruthy(); expect(result.getByTestId('case-view-status-action-button')).toBeTruthy(); - expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id, caseData.connector.id); + expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id); await waitForComponentToUpdate(); }); @@ -143,7 +152,7 @@ describe('Case View Page activity tab', () => { expect(result.getByTestId('case-tags')).toBeTruthy(); expect(result.getByTestId('connector-edit-header')).toBeTruthy(); expect(result.queryByTestId('case-view-status-action-button')).not.toBeInTheDocument(); - expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id, caseData.connector.id); + expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id); await waitForComponentToUpdate(); }); @@ -160,7 +169,7 @@ describe('Case View Page activity tab', () => { expect(result.getByTestId('case-tags')).toBeTruthy(); expect(result.getByTestId('connector-edit-header')).toBeTruthy(); expect(result.getByTestId('case-severity-selection')).toBeDisabled(); - expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id, caseData.connector.id); + expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id); await waitForComponentToUpdate(); }); @@ -174,7 +183,7 @@ describe('Case View Page activity tab', () => { const result = appMockRender.render(); expect(result.getByTestId('case-view-loading-content')).toBeTruthy(); expect(result.queryByTestId('case-view-activity')).toBeFalsy(); - expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id, caseData.connector.id); + expect(useFindCaseUserActionsMock).toHaveBeenCalledWith(caseData.id); }); it('should not render the assignees on basic license', () => { @@ -205,7 +214,8 @@ describe('Case View Page activity tab', () => { const result = appMockRender.render(); - expect(result.getByTestId('case-view-edit-connector')).toBeInTheDocument(); - await waitForComponentToUpdate(); + await waitFor(() => { + expect(result.getByTestId('case-view-edit-connector')).toBeInTheDocument(); + }); }); }); 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 6218c152e5c85..bba0106f9984f 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 @@ -5,13 +5,16 @@ * 2.0. */ +/* eslint-disable complexity */ + import { EuiFlexGroup, EuiFlexItem, EuiLoadingContent } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { isEqual, uniq } from 'lodash'; +import { useGetCaseConnectors } from '../../../containers/use_get_case_connectors'; import { useCasesFeatures } from '../../../common/use_cases_features'; import { useGetCurrentUserProfile } from '../../../containers/user_profiles/use_get_current_user_profile'; import { useBulkGetUserProfiles } from '../../../containers/user_profiles/use_bulk_get_user_profiles'; -import { useGetConnectors } from '../../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../../containers/configure/use_get_supported_action_connectors'; import type { CaseSeverity } from '../../../../common/api'; import { useCaseViewNavigation } from '../../../common/navigation'; import type { UseFetchAlertData } from '../../../../common/ui/types'; @@ -25,8 +28,6 @@ import { UserList } from './user_list'; import { useOnUpdateField } from '../use_on_update_field'; import { useCasesContext } from '../../cases_context/use_cases_context'; import * as i18n from '../translations'; -import { getNoneConnector, normalizeActionConnector } from '../../configure_cases/utils'; -import { getConnectorById } from '../../utils'; import { SeveritySidebarSelector } from '../../severity/sidebar_selector'; import { useFindCaseUserActions } from '../../../containers/use_find_case_user_actions'; import { AssignUsers } from './assign_users'; @@ -49,9 +50,12 @@ export const CaseViewActivity = ({ const { getCaseViewUrl } = useCaseViewNavigation(); const { caseAssignmentAuthorized, pushToServiceAuthorized } = useCasesFeatures(); + const { data: caseConnectors, isLoading: isLoadingCaseConnectors } = useGetCaseConnectors( + caseData.id + ); + const { data: userActionsData, isLoading: isLoadingUserActions } = useFindCaseUserActions( - caseData.id, - caseData.connector.id + caseData.id ); const assignees = useMemo( @@ -79,7 +83,6 @@ export const CaseViewActivity = ({ ); const { onUpdateField, isLoading, loadingKey } = useOnUpdateField({ - caseId: caseData.id, caseData, }); @@ -125,37 +128,38 @@ export const CaseViewActivity = ({ [assignees, onUpdateField] ); - const { isLoading: isLoadingConnectors, data: connectors = [] } = useGetConnectors(); - - const [connectorName, isValidConnector] = useMemo(() => { - const connector = connectors.find((c) => c.id === caseData.connector.id); - return [connector?.name ?? '', !!connector]; - }, [connectors, caseData.connector]); + const { isLoading: isLoadingAllAvailableConnectors, data: supportedActionConnectors } = + useGetSupportedActionConnectors(); const onSubmitConnector = useCallback( - (connectorId, connectorFields, onError, onSuccess) => { - const connector = getConnectorById(connectorId, connectors); - const connectorToUpdate = connector - ? normalizeActionConnector(connector) - : getNoneConnector(); - + (connector, onError, onSuccess) => { onUpdateField({ key: 'connector', - value: { ...connectorToUpdate, fields: connectorFields }, + value: connector, onSuccess, onError, }); }, - [onUpdateField, connectors] + [onUpdateField] ); + const showUserActions = + !isLoadingUserActions && + !isLoadingCaseConnectors && + userActionsData && + caseConnectors && + userProfiles; + + const showConnectorSidebar = + pushToServiceAuthorized && userActionsData && caseConnectors && supportedActionConnectors; + return ( <> - {isLoadingUserActions && ( + {(isLoadingUserActions || isLoadingCaseConnectors) && ( )} - {!isLoadingUserActions && userActionsData && userProfiles && ( + {showUserActions && ( - {pushToServiceAuthorized && userActionsData ? ( + {showConnectorSidebar ? ( ) : 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 369947fcc2b87..1004ce3d2f437 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 @@ -26,7 +26,7 @@ import { useGetCaseMetrics } from '../../containers/use_get_case_metrics'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; import { useKibana } from '../../common/lib/kibana'; import { useFindCaseUserActions } from '../../containers/use_find_case_user_actions'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer } from '../../common/mock'; import CaseView from '.'; @@ -49,7 +49,7 @@ jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/use_find_case_user_actions'); jest.mock('../../containers/use_get_case'); jest.mock('../../containers/use_get_case_metrics'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/use_post_push_to_service'); jest.mock('../user_actions/timestamp', () => ({ UserActionTimestamp: () => <>, @@ -62,7 +62,7 @@ const useFetchCaseMock = useGetCase as jest.Mock; const useGetCaseMetricsMock = useGetCaseMetrics as jest.Mock; const useUpdateCaseMock = useUpdateCase as jest.Mock; const useFindCaseUserActionsMock = useFindCaseUserActions as jest.Mock; -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const usePostPushToServiceMock = usePostPushToService as jest.Mock; const useKibanaMock = useKibana as jest.MockedFunction; const useGetTagsMock = useGetTags as jest.Mock; diff --git a/x-pack/plugins/cases/public/components/case_view/mocks.ts b/x-pack/plugins/cases/public/components/case_view/mocks.ts index 88069fe79cfe5..4c4bb40c81df6 100644 --- a/x-pack/plugins/cases/public/components/case_view/mocks.ts +++ b/x-pack/plugins/cases/public/components/case_view/mocks.ts @@ -102,8 +102,6 @@ export const defaultUpdateCaseState = { export const defaultUseFindCaseUserActions = { data: { caseUserActions: [...caseUserActions, getAlertUserAction()], - caseServices: {}, - hasDataToPush: false, participants: [caseData.createdBy], }, refetch: jest.fn(), diff --git a/x-pack/plugins/cases/public/components/case_view/use_on_update_field.ts b/x-pack/plugins/cases/public/components/case_view/use_on_update_field.ts index 449eb7b2ca8a9..d0fc6f98f0ce6 100644 --- a/x-pack/plugins/cases/public/components/case_view/use_on_update_field.ts +++ b/x-pack/plugins/cases/public/components/case_view/use_on_update_field.ts @@ -16,7 +16,7 @@ import { useUpdateCase } from '../../containers/use_update_case'; import { getTypedPayload } from '../../containers/utils'; import type { OnUpdateFields } from './types'; -export const useOnUpdateField = ({ caseData, caseId }: { caseData: Case; caseId: string }) => { +export const useOnUpdateField = ({ caseData }: { caseData: Case }) => { const { isLoading, updateKey: loadingKey, updateCaseProperty } = useUpdateCase(); const onUpdateField = useCallback( diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx index 2057d0153be68..19ac7d0b667c1 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx @@ -121,7 +121,7 @@ describe('Connectors', () => { newWrapper .find('button[data-test-subj="case-configure-update-selected-connector-button"]') .text() - ).toBe('Update My Connector'); + ).toBe('Update My SN connector'); }); it('shows the deprecated callout when the connector is deprecated', async () => { diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx index 3f3bdedff3e3c..9c5681c4025b6 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors_dropdown.test.tsx @@ -83,7 +83,7 @@ describe('ConnectorsDropdown', () => { grow={false} > - My Connector + My SN connector , @@ -108,7 +108,7 @@ describe('ConnectorsDropdown', () => { grow={false} > - My Connector 2 + My Resilient connector , @@ -183,7 +183,7 @@ describe('ConnectorsDropdown', () => { grow={false} > - My Connector + My deprecated SN connector (deprecated) @@ -235,7 +235,7 @@ describe('ConnectorsDropdown', () => { .find('[data-test-subj="dropdown-connectors"]') .first() .text() - .includes('My Connector, is selected') + .includes('My SN connector, is selected') ).toBeTruthy(); }); 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 2699557bb51f1..bad53d324da3c 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 @@ -28,15 +28,15 @@ import { import { ConnectorTypes } from '../../../common/api'; import { actionTypeRegistryMock } from '@kbn/triggers-actions-ui-plugin/public/application/action_type_registry.mock'; import { useGetActionTypes } from '../../containers/configure/use_action_types'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; jest.mock('../../common/lib/kibana'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/configure/use_configure'); jest.mock('../../containers/configure/use_action_types'); const useKibanaMock = useKibana as jest.Mocked; -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useCaseConfigureMock = useCaseConfigure as jest.Mock; const useGetUrlSearchMock = jest.fn(); const useGetActionTypesMock = useGetActionTypes as jest.Mock; @@ -417,7 +417,7 @@ describe('ConfigureCases', () => { expect(persistCaseConfigure).toHaveBeenCalledWith({ connector: { id: 'resilient-2', - name: 'My Connector 2', + name: 'My Resilient connector', type: ConnectorTypes.resilient, fields: null, }, @@ -440,7 +440,7 @@ describe('ConfigureCases', () => { ...useCaseConfigureResponse, connector: { id: 'resilient-2', - name: 'My connector 2', + name: 'My Resilient connector', type: ConnectorTypes.resilient, fields: null, }, @@ -459,7 +459,7 @@ describe('ConfigureCases', () => { wrapper .find('button[data-test-subj="case-configure-update-selected-connector-button"]') .text() - ).toBe('Update My Connector 2'); + ).toBe('Update My Resilient connector'); }); }); 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 ec9b8a8c7a2fb..2f7db2202a76b 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -29,7 +29,7 @@ import { HeaderPage } from '../header_page'; import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesBreadcrumbs } from '../use_breadcrumbs'; import { CasesDeepLinkId } from '../../common/navigation'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; const FormWrapper = styled.div` ${({ theme }) => css` @@ -77,7 +77,7 @@ export const ConfigureCases: React.FC = React.memo(() => { isLoading: isLoadingConnectors, data: connectors = [], refetch: refetchConnectors, - } = useGetConnectors(); + } = useGetSupportedActionConnectors(); const { isLoading: isLoadingActionTypes, data: actionTypes = [], diff --git a/x-pack/plugins/cases/public/components/connectors/card.test.tsx b/x-pack/plugins/cases/public/components/connectors/card.test.tsx index 6254150620fd4..a654821830b23 100644 --- a/x-pack/plugins/cases/public/components/connectors/card.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/card.test.tsx @@ -6,15 +6,16 @@ */ import React from 'react'; -import { mount } from 'enzyme'; +import { render, screen } from '@testing-library/react'; import { ConnectorTypes } from '../../../common/api'; import { ConnectorCard } from './card'; +import { createQueryWithMarkup } from '../../common/test_utils'; describe('ConnectorCard ', () => { - it('it does not throw when accessing the icon if the connector type is not registered', () => { + it('does not throw when accessing the icon if the connector type is not registered', () => { expect(() => - mount( + render( { ) ).not.toThrowError(); }); + + it('shows the loading spinner if loading', () => { + render( + + ); + + expect(screen.getByTestId('connector-card-loading')).toBeInTheDocument(); + expect(screen.queryByTestId('connector-card')).not.toBeInTheDocument(); + }); + + it('shows the connector title', () => { + render( + + ); + + expect(screen.getByText('My connector')).toBeInTheDocument(); + }); + + it('shows the connector list items', () => { + const listItems = [ + { title: 'item 1 title', description: 'item 1 desc' }, + { title: 'item 2 title', description: 'item 2 desc' }, + ]; + + render( + + ); + + const getByText = createQueryWithMarkup(screen.getByText); + + for (const item of listItems) { + expect(getByText(`${item.title}: ${item.description}`)).toBeInTheDocument(); + } + }); }); diff --git a/x-pack/plugins/cases/public/components/connectors/card.tsx b/x-pack/plugins/cases/public/components/connectors/card.tsx index c4cd24787b01c..a9bf98dd7cf9d 100644 --- a/x-pack/plugins/cases/public/components/connectors/card.tsx +++ b/x-pack/plugins/cases/public/components/connectors/card.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; -import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; -import styled from 'styled-components'; +import React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLoadingSpinner, EuiText } from '@elastic/eui'; import type { ConnectorTypes } from '../../../common/api'; import { useKibana } from '../../common/lib/kibana'; @@ -20,12 +19,6 @@ interface ConnectorCardProps { isLoading: boolean; } -const StyledText = styled.span` - span { - display: block; - } -`; - const ConnectorCardDisplay: React.FC = ({ connectorType, title, @@ -34,44 +27,30 @@ const ConnectorCardDisplay: React.FC = ({ }) => { const { triggersActionsUi } = useKibana().services; - const description = useMemo( - () => ( - - {listItems.length > 0 && - listItems.map((item, i) => ( - - {`${item.title}: `} - {item.description} - - ))} - - ), - [listItems] - ); - - const icon = useMemo( - () => , - // eslint-disable-next-line react-hooks/exhaustive-deps - [connectorType] - ); - return ( <> {isLoading && } {!isLoading && ( - - - + + + + + {title} + + + + + + + + {listItems.length > 0 && + listItems.map((item, i) => ( + + {`${item.title}: `} + {`${item.description}`} + + ))} - {icon} )} diff --git a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx index 0e4d92139234b..751e9dadca491 100644 --- a/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/jira/case_fields.test.tsx @@ -122,15 +122,12 @@ describe('Jira Fields', () => { connector={connector} /> ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(0).text()).toEqual( - 'Issue type: Task' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(1).text()).toEqual( - 'Parent issue: Parent Task' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(2).text()).toEqual( - 'Priority: High' - ); + + const nodes = wrapper.find('[data-test-subj="card-list-item"]').hostNodes(); + + expect(nodes.at(0).text()).toEqual('Issue type: Task'); + expect(nodes.at(1).text()).toEqual('Parent issue: Parent Task'); + expect(nodes.at(2).text()).toEqual('Priority: High'); }); test('it sets parent correctly', async () => { diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx index 2273343d119b8..95510090cada1 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_itsm_case_fields.test.tsx @@ -71,15 +71,11 @@ describe('ServiceNowITSM Fields', () => { onChoicesSuccess(mockChoices); }); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(0).text()).toEqual( - 'Urgency: 2 - High' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(1).text()).toEqual( - 'Severity: 1 - Critical' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(2).text()).toEqual( - 'Impact: 3 - Moderate' - ); + const nodes = wrapper.find('[data-test-subj="card-list-item"]').hostNodes(); + + expect(nodes.at(0).text()).toEqual('Urgency: 2 - High'); + expect(nodes.at(1).text()).toEqual('Severity: 1 - Critical'); + expect(nodes.at(2).text()).toEqual('Impact: 3 - Moderate'); }); it('transforms the categories to options correctly', async () => { diff --git a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx index ee8681c7487dc..c66d40eb6827b 100644 --- a/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx +++ b/x-pack/plugins/cases/public/components/connectors/servicenow/servicenow_sir_case_fields.test.tsx @@ -75,29 +75,17 @@ describe('ServiceNowSIR Fields', () => { act(() => { onChoicesSuccess(mockChoices); }); - wrapper.update(); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(0).text()).toEqual( - 'Destination IPs: Yes' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(1).text()).toEqual( - 'Source IPs: Yes' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(2).text()).toEqual( - 'Malware URLs: Yes' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(3).text()).toEqual( - 'Malware Hashes: Yes' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(4).text()).toEqual( - 'Priority: 1 - Critical' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(5).text()).toEqual( - 'Category: Denial of Service' - ); - expect(wrapper.find('[data-test-subj="card-list-item"]').at(6).text()).toEqual( - 'Subcategory: Single or distributed (DoS or DDoS)' - ); + wrapper.update(); + const nodes = wrapper.find('[data-test-subj="card-list-item"]').hostNodes(); + + expect(nodes.at(0).text()).toEqual('Destination IPs: Yes'); + expect(nodes.at(1).text()).toEqual('Source IPs: Yes'); + expect(nodes.at(2).text()).toEqual('Malware URLs: Yes'); + expect(nodes.at(3).text()).toEqual('Malware Hashes: Yes'); + expect(nodes.at(4).text()).toEqual('Priority: 1 - Critical'); + expect(nodes.at(5).text()).toEqual('Category: Denial of Service'); + expect(nodes.at(6).text()).toEqual('Subcategory: Single or distributed (DoS or DDoS)'); }); test('it transforms the categories to options correctly', async () => { diff --git a/x-pack/plugins/cases/public/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx index d95f264089643..8aa50a4270414 100644 --- a/x-pack/plugins/cases/public/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -22,11 +22,11 @@ import { CreateCaseForm } from './form'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { useCaseConfigureResponse } from '../configure_cases/__mock__'; import { TestProviders } from '../../common/mock'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; jest.mock('../../containers/use_get_tags'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/configure/use_configure'); jest.mock('../markdown_editor/plugins/lens/use_lens_draft_comment'); jest.mock('../app/use_available_owners', () => ({ @@ -34,7 +34,7 @@ jest.mock('../app/use_available_owners', () => ({ })); const useGetTagsMock = useGetTags as jest.Mock; -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useCaseConfigureMock = useCaseConfigure as jest.Mock; const initialCaseValue: FormProps = { diff --git a/x-pack/plugins/cases/public/components/create/form_context.test.tsx b/x-pack/plugins/cases/public/components/create/form_context.test.tsx index 37971667da37b..192de92777fb6 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.test.tsx @@ -41,7 +41,7 @@ import { usePostPushToService } from '../../containers/use_post_push_to_service' import userEvent from '@testing-library/user-event'; import { connectorsMock } from '../../common/mock/connectors'; import type { CaseAttachments } from '../../types'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; import { waitForComponentToUpdate } from '../../common/test_utils'; import { userProfiles } from '../../containers/user_profiles/api.mock'; @@ -51,7 +51,7 @@ jest.mock('../../containers/use_post_case'); jest.mock('../../containers/use_create_attachments'); jest.mock('../../containers/use_post_push_to_service'); jest.mock('../../containers/use_get_tags'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/configure/use_configure'); jest.mock('../connectors/resilient/use_get_incident_types'); jest.mock('../connectors/resilient/use_get_severity'); @@ -64,7 +64,7 @@ jest.mock('../../common/lib/kibana'); jest.mock('../../containers/user_profiles/api'); jest.mock('../../common/use_license'); -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useCaseConfigureMock = useCaseConfigure as jest.Mock; const usePostCaseMock = usePostCase as jest.Mock; const useCreateAttachmentsMock = useCreateAttachments as jest.Mock; @@ -407,7 +407,7 @@ describe('Create case', () => { subcategory: null, }, id: 'servicenow-1', - name: 'My Connector', + name: 'My SN connector', type: '.servicenow', }, }); diff --git a/x-pack/plugins/cases/public/components/create/form_context.tsx b/x-pack/plugins/cases/public/components/create/form_context.tsx index 3a1c99f6d6218..ba859b21d7b0f 100644 --- a/x-pack/plugins/cases/public/components/create/form_context.tsx +++ b/x-pack/plugins/cases/public/components/create/form_context.tsx @@ -22,7 +22,7 @@ import { useCasesContext } from '../cases_context/use_cases_context'; import { useCasesFeatures } from '../../common/use_cases_features'; import { getConnectorById } from '../utils'; import type { CaseAttachmentsWithoutOwner } from '../../types'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useCreateCaseWithAttachmentsTransaction } from '../../common/apm/use_cases_transactions'; const initialCaseValue: FormProps = { @@ -55,7 +55,8 @@ export const FormContext: React.FC = ({ attachments, initialValue, }) => { - const { data: connectors = [], isLoading: isLoadingConnectors } = useGetConnectors(); + const { data: connectors = [], isLoading: isLoadingConnectors } = + useGetSupportedActionConnectors(); const { owner, appId } = useCasesContext(); const { isSyncAlertsEnabled } = useCasesFeatures(); const { postCase } = usePostCase(); diff --git a/x-pack/plugins/cases/public/components/create/index.test.tsx b/x-pack/plugins/cases/public/components/create/index.test.tsx index 24798c114fede..07a705ef41a90 100644 --- a/x-pack/plugins/cases/public/components/create/index.test.tsx +++ b/x-pack/plugins/cases/public/components/create/index.test.tsx @@ -30,13 +30,13 @@ import { useGetFieldsByIssueTypeResponse, } from './mock'; import { CreateCase } from '.'; -import { useGetConnectors } from '../../containers/configure/use_connectors'; +import { useGetSupportedActionConnectors } from '../../containers/configure/use_get_supported_action_connectors'; import { useGetTags } from '../../containers/use_get_tags'; jest.mock('../../containers/api'); jest.mock('../../containers/user_profiles/api'); jest.mock('../../containers/use_get_tags'); -jest.mock('../../containers/configure/use_connectors'); +jest.mock('../../containers/configure/use_get_supported_action_connectors'); jest.mock('../../containers/configure/use_configure'); jest.mock('../connectors/resilient/use_get_incident_types'); jest.mock('../connectors/resilient/use_get_severity'); @@ -45,7 +45,7 @@ jest.mock('../connectors/jira/use_get_fields_by_issue_type'); jest.mock('../connectors/jira/use_get_single_issue'); jest.mock('../connectors/jira/use_get_issues'); -const useGetConnectorsMock = useGetConnectors as jest.Mock; +const useGetConnectorsMock = useGetSupportedActionConnectors as jest.Mock; const useCaseConfigureMock = useCaseConfigure as jest.Mock; const useGetTagsMock = useGetTags as jest.Mock; const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; diff --git a/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts deleted file mode 100644 index a3c13c1213e64..0000000000000 --- a/x-pack/plugins/cases/public/components/edit_connector/helpers.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ConnectorUserAction } from '../../../common/api'; -import { Actions, ConnectorTypes } from '../../../common/api'; -import type { CaseUserActions } from '../../containers/types'; -import { getConnectorFieldsFromUserActions } from './helpers'; - -const defaultJiraFields = { - issueType: '1', - parent: null, - priority: null, -}; - -describe('helpers', () => { - describe('getConnectorFieldsFromUserActions', () => { - it('returns null when it cannot find the connector id', () => { - expect(getConnectorFieldsFromUserActions('a', [])).toBeNull(); - }); - - it('returns null when it cannot find the connector id in a non empty array', () => { - expect( - getConnectorFieldsFromUserActions('a', [ - createConnectorUserAction({ - // @ts-expect-error payload missing fields - payload: { a: '1' }, - }), - ]) - ).toBeNull(); - }); - - it('returns the fields when it finds the connector id', () => { - expect(getConnectorFieldsFromUserActions('a', [createConnectorUserAction()])).toEqual( - defaultJiraFields - ); - }); - - it('returns the fields when it finds the connector id in the second user action', () => { - const expectedFields = { ...defaultJiraFields, issueType: '5' }; - - expect( - getConnectorFieldsFromUserActions('id-to-find', [ - createConnectorUserAction({}), - createConnectorUserAction({ - payload: { - connector: { - id: 'id-to-find', - name: 'test', - fields: expectedFields, - type: ConnectorTypes.jira, - }, - }, - }), - ]) - ).toEqual(expectedFields); - }); - - it('returns null when the action is not a connector', () => { - expect( - getConnectorFieldsFromUserActions('id-to-find', [ - createConnectorUserAction({ - // @ts-expect-error - type: 'not-a-connector', - }), - ]) - ).toBeNull(); - }); - }); -}); - -function createConnectorUserAction(attributes: Partial = {}): CaseUserActions { - return { - action: Actions.update, - createdBy: { username: 'user', fullName: null, email: null }, - createdAt: '2021-12-08T11:28:32.623Z', - type: 'connector', - id: '', - commentId: '', - payload: { - connector: { id: 'a', name: 'test', fields: defaultJiraFields, type: ConnectorTypes.jira }, - }, - ...attributes, - } as CaseUserActions; -} diff --git a/x-pack/plugins/cases/public/components/edit_connector/helpers.ts b/x-pack/plugins/cases/public/components/edit_connector/helpers.ts deleted file mode 100644 index 84d6984f35bbc..0000000000000 --- a/x-pack/plugins/cases/public/components/edit_connector/helpers.ts +++ /dev/null @@ -1,27 +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 { isConnectorUserAction, isCreateCaseUserAction } from '../../../common/utils/user_actions'; -import type { ConnectorTypeFields } from '../../../common/api'; -import type { CaseUserActions } from '../../containers/types'; - -export const getConnectorFieldsFromUserActions = ( - id: string, - userActions: CaseUserActions[] -): ConnectorTypeFields['fields'] => { - for (const action of [...userActions].reverse()) { - if (isConnectorUserAction(action) || isCreateCaseUserAction(action)) { - const connector = action.payload.connector; - - if (connector && id === connector.id) { - return connector.fields; - } - } - } - - return null; -}; 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 401f43ae9afff..fe24145b10d68 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 @@ -6,7 +6,6 @@ */ import React from 'react'; -import { mount } from 'enzyme'; import { render, waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; @@ -20,47 +19,58 @@ import { noPushCasesPermissions, TestProviders, } from '../../common/mock'; -import { basicCase, basicPush, caseUserActions, connectorsMock } from '../../containers/mock'; +import { basicCase, connectorsMock } from '../../containers/mock'; import type { CaseConnector } from '../../containers/configure/types'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; const onSubmit = jest.fn(); -const caseServices = { - '123': { - ...basicPush, - firstPushIndex: 0, - lastPushIndex: 0, - commentsToUpdate: [], - hasDataToPush: true, - }, -}; -const getDefaultProps = (): EditConnectorProps => { - return { - caseData: basicCase, - caseServices, - connectorName: connectorsMock[0].name, - connectors: connectorsMock, - hasDataToPush: true, - isLoading: false, - isValidConnector: true, - onSubmit, - userActions: caseUserActions, - }; +const caseConnectors = getCaseConnectorsMockResponse(); + +const defaultProps: EditConnectorProps = { + caseData: basicCase, + supportedActionConnectors: connectorsMock, + isLoading: false, + caseConnectors, + onSubmit, }; describe('EditConnector ', () => { let appMockRender: AppMockRenderer; + beforeEach(() => { jest.clearAllMocks(); appMockRender = createAppMockRenderer(); }); + it('Renders the none connector', async () => { + render( + + + + ); + + expect( + await screen.findByText( + 'To create and update a case in an external system, select a connector.' + ) + ).toBeInTheDocument(); + + userEvent.click(screen.getByTestId('connector-edit-button')); + + await waitFor(() => { + expect(screen.getAllByTestId('dropdown-connector-no-connector').length).toBeGreaterThan(0); + }); + }); + it('Renders servicenow connector from case initially', async () => { - const defaultProps = getDefaultProps(); const serviceNowProps = { ...defaultProps, caseData: { ...defaultProps.caseData, - connector: { ...defaultProps.caseData.connector, id: 'servicenow-1' }, + connector: { + ...defaultProps.caseData.connector, + id: 'servicenow-1', + }, }, }; @@ -70,54 +80,76 @@ describe('EditConnector ', () => { ); - expect(await screen.findByText('My Connector')).toBeInTheDocument(); + expect(await screen.findByText('My SN connector')).toBeInTheDocument(); }); it('Renders no connector, and then edit', async () => { - const defaultProps = getDefaultProps(); - const wrapper = mount( + render( ); - expect(wrapper.find(`[data-test-subj="has-data-to-push-button"]`).exists()).toBeTruthy(); - wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); - expect( - wrapper.find(`span[data-test-subj="dropdown-connector-no-connector"]`).last().exists() - ).toBeTruthy(); + userEvent.click(screen.getByTestId('connector-edit-button')); + + await waitFor(() => { + expect(screen.getByTestId('caseConnectors')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByTestId('dropdown-connectors')); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.update(); - wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); - await waitFor(() => wrapper.update()); + await waitFor(() => { + expect(screen.getByTestId('dropdown-connector-resilient-2')).toBeInTheDocument(); + }); - expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().exists()).toBeTruthy(); + userEvent.click(screen.getByTestId('dropdown-connector-resilient-2')); + + await waitFor(() => { + expect(screen.getByTestId('edit-connectors-submit')).toBeInTheDocument(); + }); }); it('Edit external service on submit', async () => { - const defaultProps = getDefaultProps(); - const wrapper = mount( + render( ); - wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.update(); - wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); - wrapper.update(); + userEvent.click(screen.getByTestId('connector-edit-button')); + userEvent.click(screen.getByTestId('dropdown-connectors')); - expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().exists()).toBeTruthy(); + await waitFor(() => { + expect(screen.getByTestId('dropdown-connector-resilient-2')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByTestId('dropdown-connector-resilient-2'), undefined, { + skipPointerEventsCheck: true, + }); - wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click'); - await waitFor(() => expect(onSubmit.mock.calls[0][0]).toBe('resilient-2')); + expect(screen.getByTestId('edit-connectors-submit')).toBeInTheDocument(); + + userEvent.click(screen.getByTestId('edit-connectors-submit')); + + await waitFor(() => + expect(onSubmit).toHaveBeenCalledWith( + { + fields: { + incidentTypes: null, + severityCode: null, + }, + id: 'resilient-2', + name: 'My Resilient connector', + type: '.resilient', + }, + expect.anything(), + expect.anything() + ) + ); }); it('Revert to initial external service on error', async () => { - const defaultProps = getDefaultProps(); - onSubmit.mockImplementation((connector, onSuccess, onError) => { + onSubmit.mockImplementation((connector, onError, onSuccess) => { onError(new Error('An error has occurred')); }); @@ -125,43 +157,46 @@ describe('EditConnector ', () => { ...defaultProps, caseData: { ...defaultProps.caseData, - connector: { ...defaultProps.caseData.connector, id: 'servicenow-1' }, + connector: { + ...defaultProps.caseData.connector, + id: 'servicenow-1', + }, }, }; - const wrapper = mount( + render( ); - wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + userEvent.click(screen.getByTestId('connector-edit-button')); + userEvent.click(screen.getByTestId('dropdown-connectors')); + + await waitFor(() => { + expect(screen.getByTestId('dropdown-connector-resilient-2')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByTestId('dropdown-connector-resilient-2'), undefined, { + skipPointerEventsCheck: true, + }); + + userEvent.click(screen.getByTestId('edit-connectors-submit')); + await waitFor(() => { - wrapper.update(); - wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); - wrapper.update(); - expect( - wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().exists() - ).toBeTruthy(); - wrapper.find(`[data-test-subj="edit-connectors-submit"]`).last().simulate('click'); + expect(screen.queryByTestId('edit-connectors-submit')).not.toBeInTheDocument(); }); await waitFor(() => { - wrapper.update(); - expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).exists()).toBeFalsy(); + expect(onSubmit).toHaveBeenCalled(); }); - /** - * If an error is being throw on submit the selected connector should - * be reverted to the initial one. In our test the initial one is the .servicenow-1 - * connector. The title of the .servicenow-1 connector is My Connector. - */ - expect(wrapper.text().includes('My Connector')).toBeTruthy(); + await waitFor(() => { + expect(screen.getByText('My SN connector')).toBeInTheDocument(); + }); }); it('Resets selector on cancel', async () => { - const defaultProps = getDefaultProps(); const props = { ...defaultProps, caseData: { @@ -173,57 +208,76 @@ describe('EditConnector ', () => { }, }; - const wrapper = mount( + render( ); - wrapper.find('[data-test-subj="connector-edit"] button').simulate('click'); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.update(); - wrapper.find('button[data-test-subj="dropdown-connector-resilient-2"]').simulate('click'); - wrapper.update(); - wrapper.find(`[data-test-subj="edit-connectors-cancel"]`).last().simulate('click'); + userEvent.click(screen.getByTestId('connector-edit-button')); + userEvent.click(screen.getByTestId('dropdown-connectors')); await waitFor(() => { - wrapper.update(); - expect(wrapper.find(`[data-test-subj="edit-connectors-submit"]`).exists()).toBeFalsy(); + expect(screen.getByTestId('dropdown-connector-resilient-2')).toBeInTheDocument(); }); - expect(wrapper.text().includes('My Connector')).toBeTruthy(); + userEvent.click(screen.getByTestId('dropdown-connector-resilient-2')); + userEvent.click(screen.getByTestId('edit-connectors-cancel')); + + await waitFor(() => { + expect(screen.queryByTestId('edit-connectors-submit')).not.toBeInTheDocument(); + }); + + expect(screen.getByText('My SN connector')).toBeInTheDocument(); }); - it('Renders loading spinner', async () => { - const defaultProps = getDefaultProps(); + it('disabled the edit button when is loading', async () => { const props = { ...defaultProps, isLoading: true }; - const wrapper = mount( + + render( ); - await waitFor(() => - expect(wrapper.find(`[data-test-subj="connector-loading"]`).last().exists()).toBeTruthy() + + await waitFor(() => { + expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument(); + }); + }); + + it('does not shows the callouts when is loading', async () => { + const props = { ...defaultProps, isLoading: true }; + + render( + + + ); + + await waitFor(() => { + expect(screen.queryByTestId('push-callouts')).not.toBeInTheDocument(); + }); }); it('does not allow the connector to be edited when the user does not have write permissions', async () => { - const wrapper = mount( + render( - + ); - await waitFor(() => - expect(wrapper.find(`[data-test-subj="connector-edit"]`).exists()).toBeFalsy() - ); - expect(wrapper.find(`[data-test-subj="has-data-to-push-button"]`).exists()).toBeFalsy(); + await waitFor(() => { + expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument(); + }); + + await waitFor(() => { + expect(screen.queryByTestId('push-to-external-service')).not.toBeInTheDocument(); + }); }); it('display the callout message when none is selected', async () => { - const defaultProps = getDefaultProps(); - const props = { ...defaultProps, connectors: [] }; - const result = appMockRender.render(); + // default props has the none connector as selected + const result = appMockRender.render(); await waitFor(() => { expect(result.getByTestId('push-callouts')).toBeInTheDocument(); @@ -231,7 +285,6 @@ describe('EditConnector ', () => { }); it('disables the save button until changes are done ', async () => { - const defaultProps = getDefaultProps(); const serviceNowProps = { ...defaultProps, caseData: { @@ -261,18 +314,17 @@ describe('EditConnector ', () => { // simulate changing the connector userEvent.click(result.getByTestId('dropdown-connectors')); + await waitForEuiPopoverOpen(); + userEvent.click(result.getAllByTestId('dropdown-connector-no-connector')[0]); - expect(result.getByTestId('edit-connectors-submit')).toBeEnabled(); - // this strange assertion is required because of existing race conditions inside the EditConnector component await waitFor(() => { - expect(true).toBeTruthy(); + expect(result.getByTestId('edit-connectors-submit')).toBeEnabled(); }); }); it('disables the save button when no connector is the default', async () => { - const defaultProps = getDefaultProps(); const noneConnector = { ...defaultProps, caseData: { @@ -295,18 +347,17 @@ describe('EditConnector ', () => { // simulate changing the connector userEvent.click(result.getByTestId('dropdown-connectors')); + await waitForEuiPopoverOpen(); + userEvent.click(result.getAllByTestId('dropdown-connector-resilient-2')[0]); - expect(result.getByTestId('edit-connectors-submit')).toBeEnabled(); - // this strange assertion is required because of existing race conditions inside the EditConnector component await waitFor(() => { - expect(true).toBeTruthy(); + expect(result.getByTestId('edit-connectors-submit')).toBeEnabled(); }); }); it('shows the actions permission message if the user does not have read access to actions', async () => { - const defaultProps = getDefaultProps(); appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, actions: { save: false, show: false }, @@ -319,7 +370,6 @@ describe('EditConnector ', () => { }); it('does not show the actions permission message if the user has read access to actions', async () => { - const defaultProps = getDefaultProps(); appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, actions: { save: true, show: true }, @@ -332,7 +382,6 @@ describe('EditConnector ', () => { }); it('does not show the callout if the user does not have read access to actions', async () => { - const defaultProps = getDefaultProps(); const props = { ...defaultProps, connectors: [] }; appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, @@ -347,7 +396,6 @@ describe('EditConnector ', () => { }); it('does not show the push button if the user does not have read access to actions', async () => { - const defaultProps = getDefaultProps(); appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, actions: { save: false, show: false }, @@ -355,44 +403,43 @@ describe('EditConnector ', () => { const result = appMockRender.render(); await waitFor(() => { - expect(result.queryByTestId('has-data-to-push-button')).toBe(null); + expect(result.queryByTestId('push-to-external-service')).toBe(null); }); }); it('does not show the push button if the user does not have push permissions', async () => { - const defaultProps = getDefaultProps(); - appMockRender = createAppMockRenderer({ permissions: noPushCasesPermissions() }); const result = appMockRender.render(); + await waitFor(() => { - expect(result.queryByTestId('has-data-to-push-button')).toBe(null); + expect(result.queryByTestId('push-to-external-service')).toBe(null); }); }); it('does not show the edit connectors pencil if the user does not have read access to actions', async () => { - const defaultProps = getDefaultProps(); const props = { ...defaultProps, connectors: [] }; appMockRender.coreStart.application.capabilities = { ...appMockRender.coreStart.application.capabilities, actions: { save: false, show: false }, }; - const result = appMockRender.render(); + appMockRender.render(); + await waitFor(() => { - expect(result.getByTestId('connector-edit-header')).toBeInTheDocument(); - expect(result.queryByTestId('connector-edit')).toBe(null); + expect(screen.getByTestId('connector-edit-header')).toBeInTheDocument(); + expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument(); }); }); it('does not show the edit connectors pencil if the user does not have push permissions', async () => { - const defaultProps = getDefaultProps(); const props = { ...defaultProps, connectors: [] }; appMockRender = createAppMockRenderer({ permissions: noPushCasesPermissions() }); - const result = appMockRender.render(); + appMockRender.render(); + await waitFor(() => { - expect(result.getByTestId('connector-edit-header')).toBeInTheDocument(); - expect(result.queryByTestId('connector-edit')).toBe(null); + expect(screen.getByTestId('connector-edit-header')).toBeInTheDocument(); + expect(screen.queryByTestId('connector-edit-button')).toBe(null); }); }); }); 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 25fc2a69ac4dc..8a63c9d02ad26 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/index.tsx +++ b/x-pack/plugins/cases/public/components/edit_connector/index.tsx @@ -4,7 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useEffect, useReducer, useState } from 'react'; + +/* eslint-disable complexity */ + +import React, { useCallback, useReducer } from 'react'; import deepEqual from 'fast-deep-equal'; import { EuiText, @@ -13,64 +16,34 @@ import { EuiFlexItem, EuiButton, EuiButtonEmpty, - EuiLoadingSpinner, EuiButtonIcon, } from '@elastic/eui'; -import styled from 'styled-components'; import { isEmpty, noop } from 'lodash/fp'; import type { FieldConfig } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { Form, UseField, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; -import type { Case } from '../../../common/ui/types'; -import type { ActionConnector, ConnectorTypeFields } from '../../../common/api'; +import type { Case, CaseConnectors } from '../../../common/ui/types'; +import type { ActionConnector, CaseConnector, ConnectorTypeFields } from '../../../common/api'; import { NONE_CONNECTOR_ID } from '../../../common/api'; import { ConnectorSelector } from '../connector_selector/form'; import { ConnectorFieldsForm } from '../connectors/fields_form'; -import type { CaseUserActions } from '../../containers/types'; import { schema } from './schema'; -import { getConnectorFieldsFromUserActions } from './helpers'; import * as i18n from './translations'; import { getConnectorById, getConnectorsFormValidators } from '../utils'; import { usePushToService } from '../use_push_to_service'; -import type { CaseServices } from '../../containers/use_find_case_user_actions'; import { useApplicationCapabilities } from '../../common/lib/kibana'; -import { useCasesContext } from '../cases_context/use_cases_context'; +import { PushButton } from './push_button'; +import { PushCallouts } from './push_callouts'; +import { normalizeActionConnector, getNoneConnector } from '../configure_cases/utils'; export interface EditConnectorProps { caseData: Case; - caseServices: CaseServices; - connectorName: string; - connectors: ActionConnector[]; - hasDataToPush: boolean; + caseConnectors: CaseConnectors; + supportedActionConnectors: ActionConnector[]; isLoading: boolean; - isValidConnector: boolean; - onSubmit: ( - connectorId: string, - connectorFields: ConnectorTypeFields['fields'], - onError: () => void, - onSuccess: () => void - ) => void; - userActions: CaseUserActions[]; + onSubmit: (connector: CaseConnector, onError: () => void, onSuccess: () => void) => void; } -const MyFlexGroup = styled(EuiFlexGroup)` - ${({ theme }) => ` - p { - font-size: ${theme.eui.euiSizeM}; - } - `} -`; -const DisappearingFlexItem = styled(EuiFlexItem)` - ${({ $isHidden }: { $isHidden: boolean }) => - $isHidden && - ` - margin: 0 !important; - & .euiFlexItem { - margin: 0 !important; - } - `} -`; - interface State { currentConnector: ActionConnector | null; fields: ConnectorTypeFields['fields']; @@ -81,6 +54,7 @@ type Action = | { type: 'SET_CURRENT_CONNECTOR'; payload: State['currentConnector'] } | { type: 'SET_FIELDS'; payload: State['fields'] } | { type: 'SET_EDIT_CONNECTOR'; payload: State['editConnector'] }; + const editConnectorReducer = (state: State, action: Action) => { switch (action.type) { case 'SET_CURRENT_CONNECTOR': @@ -112,18 +86,15 @@ const initialState = { export const EditConnector = React.memo( ({ caseData, - caseServices, - connectorName, - connectors, - hasDataToPush, + caseConnectors, + supportedActionConnectors, isLoading, - isValidConnector, onSubmit, - userActions, }: EditConnectorProps) => { - const { permissions } = useCasesContext(); const caseFields = caseData.connector.fields; const selectedConnector = caseData.connector.id; + const actionConnector = getConnectorById(caseData.connector.id, supportedActionConnectors); + const isValidConnector = !!actionConnector; const { form } = useForm({ defaultValue: { connectorId: selectedConnector }, @@ -133,79 +104,46 @@ export const EditConnector = React.memo( const { actions } = useApplicationCapabilities(); const actionsReadCapabilities = actions.read; - // by default save if disabled - const [enableSave, setEnableSave] = useState(false); - const { setFieldValue, submit } = form; const [{ currentConnector, fields, editConnector }, dispatch] = useReducer( editConnectorReducer, - { ...initialState, fields: caseFields } - ); - - // only enable the save button if changes were made to the previous selected - // connector or its fields - useEffect(() => { - // null and none are equivalent to `no connector`. - // This makes sure we don't enable the button when the "no connector" option is selected - // by default. e.g. when a case is created without a selector - const isNoConnectorDeafultValue = - currentConnector === null && selectedConnector === NONE_CONNECTOR_ID; - const enable = - (!isNoConnectorDeafultValue && currentConnector?.id !== selectedConnector) || - !deepEqual(fields, caseFields); - - setEnableSave(enable); - }, [caseFields, currentConnector, fields, selectedConnector]); - - useEffect(() => { - // Initialize the current connector with the connector information attached to the case if we can find that - // connector in the retrieved connectors from the API call - if (!isLoading) { - dispatch({ - type: 'SET_CURRENT_CONNECTOR', - payload: getConnectorById(caseData.connector.id, connectors), - }); - - // Set the fields initially to whatever is present in the case, this should match with - // the latest user action for an update connector as well - dispatch({ - type: 'SET_FIELDS', - payload: caseFields, - }); + { + ...initialState, + fields: caseFields, + currentConnector: actionConnector, } - }, [caseData.connector.id, connectors, isLoading, caseFields]); + ); /** - * There is a race condition with this callback. At some point during the initial mounting of this component, this - * callback will be called. There are a couple problems with this: - * - * 1. If the call occurs before the above useEffect does its dispatches (aka while the connectors are still loading) this will - * result in setting the current connector to null when in fact we might have a valid connector. It could also - * cause issues when setting the fields because if there are no user actions then the getConnectorFieldsFromUserActions - * will return null even when the caseData.connector.fields is valid and populated. - * - * 2. If the call occurs after the above useEffect then the currentConnector should === newConnectorId - * - * As far as I know dispatch is synchronous so if the useEffect runs first it should successfully set currentConnector. If - * onChangeConnector runs first and sets stuff to null, then when useEffect runs it'll switch everything back to what we need it to be - * initially. + * only enable the save button if changes were made to the previous selected + * connector or its fields + * null and none are equivalent to `no connector`. + * This makes sure we don't enable the button when the "no connector" option is selected + * by default. e.g. when a case is created without a connector */ + const isDefaultNoneConnectorSelected = + currentConnector === null && selectedConnector === NONE_CONNECTOR_ID; + + const enableSave = + (!isDefaultNoneConnectorSelected && currentConnector?.id !== selectedConnector) || + !deepEqual(fields, caseFields); + const onChangeConnector = useCallback( (newConnectorId) => { // change connector on dropdown action if (currentConnector?.id !== newConnectorId) { dispatch({ type: 'SET_CURRENT_CONNECTOR', - payload: getConnectorById(newConnectorId, connectors), + payload: getConnectorById(newConnectorId, supportedActionConnectors), }); dispatch({ type: 'SET_FIELDS', - payload: getConnectorFieldsFromUserActions(newConnectorId, userActions ?? []), + payload: caseConnectors[newConnectorId]?.fields ?? null, }); } }, - [currentConnector, userActions, connectors] + [currentConnector, caseConnectors, supportedActionConnectors] ); const onFieldsChange = useCallback( @@ -220,36 +158,51 @@ export const EditConnector = React.memo( [fields, dispatch] ); - const onError = useCallback(() => { + const resetConnector = useCallback(() => { setFieldValue('connectorId', selectedConnector); + dispatch({ - type: 'SET_EDIT_CONNECTOR', - payload: false, + type: 'SET_CURRENT_CONNECTOR', + payload: actionConnector, }); - }, [dispatch, setFieldValue, selectedConnector]); - const onCancelConnector = useCallback(() => { - setFieldValue('connectorId', selectedConnector); dispatch({ type: 'SET_FIELDS', payload: caseFields, }); + dispatch({ type: 'SET_EDIT_CONNECTOR', payload: false, }); - }, [dispatch, selectedConnector, setFieldValue, caseFields]); + }, [actionConnector, caseFields, selectedConnector, setFieldValue]); + + const onError = useCallback(() => { + resetConnector(); + }, [resetConnector]); + + const onCancelConnector = useCallback(() => { + resetConnector(); + }, [resetConnector]); const onSubmitConnector = useCallback(async () => { const { isValid, data: newData } = await submit(); + if (isValid && newData.connectorId) { - onSubmit(newData.connectorId, fields, onError, noop); + const connector = getConnectorById(newData.connectorId, supportedActionConnectors); + const connectorToUpdate = connector + ? normalizeActionConnector(connector) + : getNoneConnector(); + + const connectorWithFields = { ...connectorToUpdate, fields } as CaseConnector; + onSubmit(connectorWithFields, onError, noop); + dispatch({ type: 'SET_EDIT_CONNECTOR', payload: false, }); } - }, [dispatch, submit, fields, onSubmit, onError]); + }, [submit, supportedActionConnectors, fields, onSubmit, onError]); const onEditClick = useCallback(() => { dispatch({ @@ -260,27 +213,42 @@ export const EditConnector = React.memo( const connectorIdConfig = getConnectorsFormValidators({ config: schema.connectorId as FieldConfig, - connectors, + connectors: supportedActionConnectors, }); - const { pushButton, pushCallouts } = usePushToService({ - connector: { - ...caseData.connector, - name: isEmpty(connectorName) ? caseData.connector.name : connectorName, - }, - caseServices, + const connectorWithName = { + ...caseData.connector, + name: isEmpty(actionConnector?.name) ? caseData.connector.name : actionConnector?.name ?? '', + }; + + const { + errorsMsg, + needsToBePushed, + hasBeenPushed, + isLoading: isLoadingPushToService, + hasPushPermissions, + hasErrorMessages, + hasLicenseError, + handlePushToService, + } = usePushToService({ + connector: connectorWithName, + caseConnectors, caseId: caseData.id, caseStatus: caseData.status, - connectors, - hasDataToPush, - onEditClick, isValidConnector, }); + const disablePushButton = + isLoadingPushToService || + errorsMsg.length > 0 || + !hasPushPermissions || + !isValidConnector || + !needsToBePushed; + return ( -

      {i18n.CONNECTORS}

      - {isLoading && } - {!isLoading && !editConnector && permissions.push && actionsReadCapabilities && ( + {!isLoading && !editConnector && hasPushPermissions && actionsReadCapabilities ? ( - )} - + ) : null} + - - {!isLoading && !editConnector && pushCallouts && actionsReadCapabilities && ( - {pushCallouts} + + {!isLoading && !editConnector && hasErrorMessages && actionsReadCapabilities && ( + + 0} + onEditClick={onEditClick} + /> + )} - -
      + + - -
      + + {!editConnector && !actionsReadCapabilities && ( @@ -372,16 +346,26 @@ export const EditConnector = React.memo(
      )} - {pushCallouts == null && + {!hasErrorMessages && !isLoading && !editConnector && - permissions.push && + hasPushPermissions && actionsReadCapabilities && ( - - {pushButton} + + + 0 || !needsToBePushed || !hasPushPermissions} + connectorName={connectorWithName.name} + /> + )} -
      + ); diff --git a/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx new file mode 100644 index 0000000000000..fee6fdc8d1557 --- /dev/null +++ b/x-pack/plugins/cases/public/components/edit_connector/push_button.test.tsx @@ -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 React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { PushButton } from './push_button'; + +const pushToService = jest.fn(); + +const defaultProps = { + disabled: false, + isLoading: false, + errorsMsg: [], + hasBeenPushed: false, + showTooltip: false, + connectorName: 'My SN connector', + pushToService, +}; + +describe('PushButton ', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders the button without tooltip', async () => { + appMockRender.render(); + + expect(screen.getByTestId('push-to-external-service')).toBeInTheDocument(); + expect(screen.queryByTestId('push-button-tooltip')).not.toBeInTheDocument(); + }); + + it('renders the correct label when the connector has not been pushed', async () => { + appMockRender.render(); + + expect(screen.getByText('Push as My SN connector incident')).toBeInTheDocument(); + }); + + it('renders the correct label when the connector has been pushed', async () => { + appMockRender.render(); + + expect(screen.getByText('Update My SN connector incident')).toBeInTheDocument(); + }); + + it('pushed correctly', async () => { + appMockRender.render(); + + userEvent.click(screen.getByTestId('push-to-external-service')); + expect(pushToService).toHaveBeenCalled(); + }); + + it('disables the button', async () => { + appMockRender.render(); + + expect(screen.getByTestId('push-to-external-service')).toBeDisabled(); + }); + + it('shows the tooltip context correctly', async () => { + appMockRender.render(); + + userEvent.hover(screen.getByTestId('push-to-external-service')); + + expect(await screen.findByText('My SN connector incident is up to date')).toBeInTheDocument(); + expect(await screen.findByText('No update is required')).toBeInTheDocument(); + }); + + it('shows the tooltip context correctly with custom message', async () => { + appMockRender.render( + + ); + + userEvent.hover(screen.getByTestId('push-to-external-service')); + + expect(await screen.findByText('My title')).toBeInTheDocument(); + expect(await screen.findByText('My desc')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/edit_connector/push_button.tsx b/x-pack/plugins/cases/public/components/edit_connector/push_button.tsx new file mode 100644 index 0000000000000..d004d69da85cf --- /dev/null +++ b/x-pack/plugins/cases/public/components/edit_connector/push_button.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 React from 'react'; +import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; +import type { ErrorMessage } from '../use_push_to_service/callout/types'; +import * as i18n from './translations'; + +interface PushButtonProps { + isLoading: boolean; + disabled: boolean; + errorsMsg: ErrorMessage[]; + hasBeenPushed: boolean; + showTooltip: boolean; + connectorName: string; + pushToService: () => Promise; +} + +const PushButtonComponent: React.FC = ({ + disabled, + errorsMsg, + isLoading, + hasBeenPushed, + connectorName, + showTooltip, + pushToService, +}) => { + const button = ( + + {hasBeenPushed ? i18n.UPDATE_INCIDENT(connectorName) : i18n.PUSH_INCIDENT(connectorName)} + + ); + + return showTooltip ? ( + 0 ? errorsMsg[0].title : i18n.PUSH_LOCKED_TITLE(connectorName)} + content={

      {errorsMsg.length > 0 ? errorsMsg[0].description : i18n.PUSH_LOCKED_DESC}

      } + data-test-subj="push-button-tooltip" + > + {button} +
      + ) : ( + <>{button} + ); +}; + +PushButtonComponent.displayName = 'PushButton'; + +export const PushButton = React.memo(PushButtonComponent); diff --git a/x-pack/plugins/cases/public/components/edit_connector/push_callouts.test.tsx b/x-pack/plugins/cases/public/components/edit_connector/push_callouts.test.tsx new file mode 100644 index 0000000000000..46e2d56fbce9a --- /dev/null +++ b/x-pack/plugins/cases/public/components/edit_connector/push_callouts.test.tsx @@ -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 React from 'react'; +import { screen } from '@testing-library/react'; + +import type { AppMockRenderer } from '../../common/mock'; +import { createAppMockRenderer } from '../../common/mock'; +import { PushCallouts } from './push_callouts'; + +const onEditClick = jest.fn(); + +const defaultProps = { + hasConnectors: false, + hasLicenseError: false, + errorsMsg: [{ id: 'test-id', title: 'My title', description: 'My desc' }], + onEditClick, +}; + +describe('PushCallouts ', () => { + let appMockRender: AppMockRenderer; + + beforeEach(() => { + jest.clearAllMocks(); + appMockRender = createAppMockRenderer(); + }); + + it('renders', async () => { + appMockRender.render(); + + expect(await screen.findByText('My title')).toBeInTheDocument(); + expect(await screen.findByText('My desc')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/cases/public/components/edit_connector/push_callouts.tsx b/x-pack/plugins/cases/public/components/edit_connector/push_callouts.tsx new file mode 100644 index 0000000000000..65ea0f20a7e82 --- /dev/null +++ b/x-pack/plugins/cases/public/components/edit_connector/push_callouts.tsx @@ -0,0 +1,37 @@ +/* + * 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 { CaseCallOut } from '../use_push_to_service/callout'; +import type { ErrorMessage } from '../use_push_to_service/callout/types'; + +interface PushCalloutsProps { + hasConnectors: boolean; + hasLicenseError: boolean; + errorsMsg: ErrorMessage[]; + onEditClick: () => void; +} + +const PushCalloutsComponent: React.FC = ({ + hasConnectors, + hasLicenseError, + errorsMsg, + onEditClick, +}) => { + return ( + + ); +}; + +PushCalloutsComponent.displayName = 'PushCalloutsComponent'; + +export const PushCallouts = React.memo(PushCalloutsComponent); diff --git a/x-pack/plugins/cases/public/components/edit_connector/translations.ts b/x-pack/plugins/cases/public/components/edit_connector/translations.ts index ab69c94321703..532e0bb70e093 100644 --- a/x-pack/plugins/cases/public/components/edit_connector/translations.ts +++ b/x-pack/plugins/cases/public/components/edit_connector/translations.ts @@ -8,6 +8,12 @@ import { i18n } from '@kbn/i18n'; export * from '../../common/translations'; +export { + UPDATE_INCIDENT, + PUSH_INCIDENT, + PUSH_LOCKED_TITLE, + PUSH_LOCKED_DESC, +} from '../use_push_to_service/translations'; export const EDIT_CONNECTOR_ARIA = i18n.translate( 'xpack.cases.editConnector.editConnectorLinkAria', diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx index 08192a1efc68f..07f4ab2768c0b 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/callout/index.tsx @@ -67,25 +67,26 @@ const CaseCallOutComponent = ({ [messages] ); + const groupedByTypeErrorMessagesKeys = Object.keys(groupedByTypeErrorMessages) as Array< + keyof ErrorMessage['errorType'] + >; return ( <> - {(Object.keys(groupedByTypeErrorMessages) as Array).map( - (type: NonNullable) => { - const id = createCalloutId(groupedByTypeErrorMessages[type].messagesId); - return ( - - - - - ); - } - )} + {groupedByTypeErrorMessagesKeys.map((type: NonNullable, index) => { + const id = createCalloutId(groupedByTypeErrorMessages[type].messagesId); + return ( + + + {index !== groupedByTypeErrorMessagesKeys.length - 1 ? : null} + + ); + })} ); }; diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx b/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx index 0a58678da6d0e..f2f19a91d11e5 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx +++ b/x-pack/plugins/cases/public/components/use_push_to_service/helpers.tsx @@ -42,12 +42,15 @@ export const getKibanaConfigError = () => ({ title: i18n.PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE, description: ( - {'coming soon...'} + + {i18n.LINK_ACTIONS_CONFIGURATION} ), }} 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 2cd381e7035b2..52598a80f000e 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 @@ -7,18 +7,19 @@ import React from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; -import { render, screen } from '@testing-library/react'; import '../../common/mock/match_media'; import type { ReturnUsePushToService, UsePushToService } from '.'; import { usePushToService } from '.'; import { noPushCasesPermissions, readCasesPermissions, TestProviders } from '../../common/mock'; +import type { CaseConnector } from '../../../common/api'; import { CaseStatuses, ConnectorTypes } from '../../../common/api'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import { basicPush, actionLicenses, connectorsMock } from '../../containers/mock'; +import { actionLicenses } from '../../containers/mock'; import { CLOSED_CASE_PUSH_ERROR_ID } from './callout/types'; -import * as i18n from './translations'; import { useGetActionLicense } from '../../containers/use_get_action_license'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; +import { useRefreshCaseViewPage } from '../case_view/use_on_refresh_case_view_page'; jest.mock('../../containers/use_get_action_license', () => { return { @@ -28,80 +29,60 @@ jest.mock('../../containers/use_get_action_license', () => { jest.mock('../../containers/use_post_push_to_service'); jest.mock('../../containers/configure/api'); jest.mock('../../common/navigation/hooks'); +jest.mock('../case_view/use_on_refresh_case_view_page'); const useFetchActionLicenseMock = useGetActionLicense as jest.Mock; +const usePostPushToServiceMock = usePostPushToService as jest.Mock; describe('usePushToService', () => { const caseId = '12345'; - const onEditClick = jest.fn(); - const pushCaseToExternalService = jest.fn(); + const pushCaseToExternalService = jest.fn().mockReturnValue({}); const mockPostPush = { isLoading: false, pushCaseToExternalService, }; - const mockConnector = connectorsMock[0]; + const caseConnectors = getCaseConnectorsMockResponse(); + const mockConnector = caseConnectors['jira-1']; const actionLicense = actionLicenses[0]; - const caseServices = { - '123': { - ...basicPush, - firstPushIndex: 0, - lastPushIndex: 0, - commentsToUpdate: [], - hasDataToPush: true, - }, - }; const defaultArgs = { - actionsErrors: [], + caseId, + caseStatus: CaseStatuses.open, connector: { id: mockConnector.id, name: mockConnector.name, - type: ConnectorTypes.serviceNowITSM, - fields: null, - }, - caseId, - caseServices, - caseStatus: CaseStatuses.open, - configureCasesNavigation: { - href: 'href', - onClick: jest.fn(), - }, - connectors: connectorsMock, - hasDataToPush: true, - onEditClick, + type: mockConnector.type, + fields: mockConnector.fields, + } as CaseConnector, + caseConnectors, isValidConnector: true, }; beforeEach(() => { jest.clearAllMocks(); - (usePostPushToService as jest.Mock).mockImplementation(() => mockPostPush); - useFetchActionLicenseMock.mockImplementation(() => ({ + usePostPushToServiceMock.mockReturnValue(mockPostPush); + useFetchActionLicenseMock.mockReturnValue({ isLoading: false, data: actionLicense, - })); + }); }); - it('push case button posts the push with correct args', async () => { + it('calls pushCaseToExternalService with correct arguments', async () => { + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePushToService(defaultArgs), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - result.current.pushButton.props.children.props.onClick(); - expect(pushCaseToExternalService).toBeCalledWith({ - caseId, - connector: { - fields: null, - id: 'servicenow-1', - name: 'My Connector', - type: ConnectorTypes.serviceNowITSM, - }, - }); - expect(result.current.pushCallouts).toBeNull(); + await result.current.handlePushToService(); + }); + + expect(pushCaseToExternalService).toBeCalledWith({ + caseId, + connector: defaultArgs.connector, }); }); @@ -113,18 +94,18 @@ describe('usePushToService', () => { enabledInLicense: false, }, })); - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePushToService(defaultArgs), - { - wrapper: ({ children }) => {children}, - } - ); - await waitForNextUpdate(); - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual('license-error'); - }); + + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + + const errorsMsg = result.current.errorsMsg; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].id).toEqual('license-error'); + expect(result.current.hasErrorMessages).toBe(true); }); it('Displays message when user does not have case enabled in config', async () => { @@ -136,28 +117,188 @@ describe('usePushToService', () => { }, })); + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + + const errorsMsg = result.current.errorsMsg; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].id).toEqual('kibana-config-error'); + expect(result.current.hasErrorMessages).toBe(true); + }); + + it('Displays message when user has select none as connector', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + const errorsMsg = result.current.errorsMsg; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].id).toEqual('connector-missing-error'); + expect(result.current.hasErrorMessages).toBe(true); + }); + + it('Displays message when connector is deleted', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + connector: { + id: 'not-exist', + name: 'not-exist', + type: ConnectorTypes.none, + fields: null, + }, + isValidConnector: false, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + const errorsMsg = result.current.errorsMsg; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].id).toEqual('connector-deleted-error'); + expect(result.current.hasErrorMessages).toBe(true); + }); + + it('Displays message when case is closed', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + caseStatus: CaseStatuses.closed, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + const errorsMsg = result.current.errorsMsg; + expect(errorsMsg).toHaveLength(1); + expect(errorsMsg[0].id).toEqual(CLOSED_CASE_PUSH_ERROR_ID); + expect(result.current.hasErrorMessages).toBe(true); + }); + + it('should not call pushCaseToExternalService when the selected connector is none', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + connector: { + id: 'none', + name: 'none', + type: ConnectorTypes.none, + fields: null, + }, + }), + { + wrapper: ({ children }) => {children}, + } + ); + await act(async () => { - const { result, waitForNextUpdate } = renderHook( + await result.current.handlePushToService(); + }); + + expect(pushCaseToExternalService).not.toBeCalled(); + }); + + it('refresh case view page after push', async () => { + const { result, waitFor } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + + await act(async () => { + await result.current.handlePushToService(); + }); + + await waitFor(() => { + expect(useRefreshCaseViewPage()).toHaveBeenCalled(); + }); + }); + + describe('user does not have write or push permissions', () => { + it('returns correct information about push permissions', async () => { + const { result } = renderHook( () => usePushToService(defaultArgs), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + {children} + ), } ); - await waitForNextUpdate(); - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual('kibana-config-error'); + expect(result.current.hasPushPermissions).toBe(false); }); - }); - it('Displays message when user does not have any connector configured', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( + it('does not display a message when user does not have a premium license', async () => { + useFetchActionLicenseMock.mockImplementation(() => ({ + isLoading: false, + data: { + ...actionLicense, + enabledInLicense: false, + }, + })); + + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); + + expect(result.current.errorsMsg).toEqual([]); + expect(result.current.hasErrorMessages).toBe(false); + }); + + it('does not display a message when user does not have case enabled in config', async () => { + useFetchActionLicenseMock.mockImplementation(() => ({ + isLoading: false, + data: { + ...actionLicense, + enabledInConfig: false, + }, + })); + + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); + + expect(result.current.errorsMsg).toEqual([]); + expect(result.current.hasErrorMessages).toBe(false); + }); + + it('does not display a message when user does not have any connector configured', async () => { + const { result } = renderHook( () => usePushToService({ ...defaultArgs, - connectors: [], connector: { id: 'none', name: 'none', @@ -166,24 +307,18 @@ describe('usePushToService', () => { }, }), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + {children} + ), } ); - await waitForNextUpdate(); - - render(result.current.pushCallouts ?? <>); - // getByText will thrown an error if the element is not found. - screen.getByText(i18n.CONFIGURE_CONNECTOR); - - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); + expect(result.current.errorsMsg).toEqual([]); + expect(result.current.hasErrorMessages).toBe(false); }); - }); - it('Displays message when user does have a connector but is configured to none', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( + it('does not display a message when user does have a connector but is configured to none', async () => { + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -195,24 +330,18 @@ describe('usePushToService', () => { }, }), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + {children} + ), } ); - await waitForNextUpdate(); - - render(result.current.pushCallouts ?? <>); - // getByText will thrown an error if the element is not found. - screen.getByText(i18n.CONFIGURE_CONNECTOR); - - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); + expect(result.current.errorsMsg).toEqual([]); + expect(result.current.hasErrorMessages).toBe(false); }); - }); - it('Displays message when connector is deleted', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( + it('does not display a message when connector is deleted', async () => { + const { result } = renderHook( () => usePushToService({ ...defaultArgs, @@ -225,23 +354,125 @@ describe('usePushToService', () => { isValidConnector: false, }), { - wrapper: ({ children }) => {children}, + wrapper: ({ children }) => ( + {children} + ), } ); - await waitForNextUpdate(); - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual('connector-deleted-error'); + + expect(result.current.errorsMsg).toEqual([]); + expect(result.current.hasErrorMessages).toBe(false); + }); + + it('does not display a message when case is closed', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + caseStatus: CaseStatuses.closed, + }), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); + + expect(result.current.errorsMsg).toEqual([]); + expect(result.current.hasErrorMessages).toBe(false); }); }); - it('Displays message when connector is deleted with empty connectors', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( + describe('returned values', () => { + it('initial', async () => { + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + + const { handlePushToService, errorsMsg, ...rest } = result.current; + + expect(rest).toEqual({ + hasBeenPushed: true, + hasErrorMessages: false, + hasLicenseError: false, + hasPushPermissions: true, + isLoading: false, + needsToBePushed: false, + }); + }); + + it('isLoading is true when usePostPushToService is loading', async () => { + usePostPushToServiceMock.mockReturnValue({ ...mockPostPush, isLoading: true }); + + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + + expect(result.current.isLoading).toBe(true); + }); + + it('isLoading is true when loading license', async () => { + useFetchActionLicenseMock.mockReturnValue({ + isLoading: true, + data: actionLicense, + }); + + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); + + expect(result.current.isLoading).toBe(true); + }); + + it('hasErrorMessages=true if there are error messages', async () => { + const { result } = renderHook( + () => usePushToService({ ...defaultArgs, caseStatus: CaseStatuses.closed }), + { + wrapper: ({ children }) => {children}, + } + ); + + expect(result.current.hasErrorMessages).toBe(true); + }); + + it('needsToBePushed=true if the connector needs to be pushed', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + caseConnectors: { + ...caseConnectors, + [mockConnector.id]: { + ...caseConnectors[mockConnector.id], + push: { + ...caseConnectors[mockConnector.id].push, + needsToBePushed: true, + }, + }, + }, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + expect(result.current.needsToBePushed).toBe(true); + }); + + it('needsToBePushed=false if the connector does not exist', async () => { + const { result } = renderHook( () => usePushToService({ ...defaultArgs, - connectors: [], connector: { id: 'not-exist', name: 'not-exist', @@ -254,212 +485,106 @@ describe('usePushToService', () => { wrapper: ({ children }) => {children}, } ); - await waitForNextUpdate(); - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual('connector-deleted-error'); + + expect(result.current.needsToBePushed).toBe(false); }); - }); - it('Displays message when case is closed', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( + it('hasBeenPushed=false if the connector has been pushed', async () => { + const { result } = renderHook( () => usePushToService({ ...defaultArgs, - caseStatus: CaseStatuses.closed, + caseConnectors: { + ...caseConnectors, + [mockConnector.id]: { + ...caseConnectors[mockConnector.id], + push: { + ...caseConnectors[mockConnector.id].push, + hasBeenPushed: false, + }, + }, + }, }), { wrapper: ({ children }) => {children}, } ); - await waitForNextUpdate(); - const errorsMsg = result.current.pushCallouts?.props.messages; - expect(errorsMsg).toHaveLength(1); - expect(errorsMsg[0].id).toEqual(CLOSED_CASE_PUSH_ERROR_ID); + + expect(result.current.hasBeenPushed).toBe(false); }); - }); - describe('user does not have write permissions', () => { - it('disables the push button when the user does not have push permissions', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePushToService(defaultArgs), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - - const { getByTestId } = render(result.current.pushButton); - - expect(getByTestId('push-to-external-service')).toBeDisabled(); - }); + it('hasBeenPushed=false if the connector does not exist', async () => { + const { result } = renderHook( + () => + usePushToService({ + ...defaultArgs, + connector: { + id: 'not-exist', + name: 'not-exist', + type: ConnectorTypes.none, + fields: null, + }, + isValidConnector: false, + }), + { + wrapper: ({ children }) => {children}, + } + ); + + expect(result.current.hasBeenPushed).toBe(false); }); - it('does not display a message when user does not have a premium license', async () => { - useFetchActionLicenseMock.mockImplementation(() => ({ - isLoading: false, - data: { - ...actionLicense, - enabledInLicense: false, - }, - })); - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePushToService(defaultArgs), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); + it('hasPushPermissions=false if it does not have push permission', async () => { + useFetchActionLicenseMock.mockReturnValue({ + isLoading: true, + data: actionLicense, }); + + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => ( + {children} + ), + } + ); + + expect(result.current.hasPushPermissions).toBe(false); }); - it('does not display a message when user does not have case enabled in config', async () => { + it('hasLicenseError=true if enabledInLicense=false', async () => { useFetchActionLicenseMock.mockImplementation(() => ({ isLoading: false, data: { ...actionLicense, - enabledInConfig: false, + enabledInLicense: false, }, })); - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => usePushToService(defaultArgs), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); - }); - }); - it('does not display a message when user does not have any connector configured', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => - usePushToService({ - ...defaultArgs, - connectors: [], - connector: { - id: 'none', - name: 'none', - type: ConnectorTypes.none, - fields: null, - }, - }), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); - }); - }); + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); - it('does not display a message when user does have a connector but is configured to none', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => - usePushToService({ - ...defaultArgs, - connector: { - id: 'none', - name: 'none', - type: ConnectorTypes.none, - fields: null, - }, - }), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); - }); + expect(result.current.hasLicenseError).toBe(true); }); - it('does not display a message when connector is deleted', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => - usePushToService({ - ...defaultArgs, - connector: { - id: 'not-exist', - name: 'not-exist', - type: ConnectorTypes.none, - fields: null, - }, - isValidConnector: false, - }), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); - }); - }); + it('hasLicenseError=false if data=undefined', async () => { + useFetchActionLicenseMock.mockImplementation(() => ({ + isLoading: false, + data: undefined, + })); - it('does not display a message when connector is deleted with empty connectors', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => - usePushToService({ - ...defaultArgs, - connectors: [], - connector: { - id: 'not-exist', - name: 'not-exist', - type: ConnectorTypes.none, - fields: null, - }, - isValidConnector: false, - }), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); - }); - }); + const { result } = renderHook( + () => usePushToService(defaultArgs), + { + wrapper: ({ children }) => {children}, + } + ); - it('does not display a message when case is closed', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook( - () => - usePushToService({ - ...defaultArgs, - caseStatus: CaseStatuses.closed, - }), - { - wrapper: ({ children }) => ( - {children} - ), - } - ); - await waitForNextUpdate(); - expect(result.current.pushCallouts).toBeNull(); - }); + expect(result.current.hasLicenseError).toBe(false); }); }); }); 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 71a6184c7286f..b31a4faa80524 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 @@ -5,11 +5,9 @@ * 2.0. */ -import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { usePostPushToService } from '../../containers/use_post_push_to_service'; -import { CaseCallOut } from './callout'; import { getLicenseError, getKibanaConfigError, @@ -17,47 +15,48 @@ import { getDeletedConnectorError, getCaseClosedInfo, } from './helpers'; -import * as i18n from './translations'; -import type { CaseConnector, ActionConnector } from '../../../common/api'; +import type { CaseConnector } from '../../../common/api'; import { CaseStatuses } from '../../../common/api'; -import type { CaseServices } from '../../containers/use_find_case_user_actions'; import type { 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'; +import type { CaseConnectors } from '../../containers/types'; export interface UsePushToService { + caseConnectors: CaseConnectors; caseId: string; - caseServices: CaseServices; caseStatus: string; connector: CaseConnector; - connectors: ActionConnector[]; - hasDataToPush: boolean; isValidConnector: boolean; - onEditClick: () => void; } export interface ReturnUsePushToService { - pushButton: JSX.Element; - pushCallouts: JSX.Element | null; + errorsMsg: ErrorMessage[]; + hasBeenPushed: boolean; + needsToBePushed: boolean; + hasPushPermissions: boolean; + isLoading: boolean; + hasErrorMessages: boolean; + hasLicenseError: boolean; + handlePushToService: () => Promise; } export const usePushToService = ({ caseId, - caseServices, caseStatus, + caseConnectors, connector, - connectors, - hasDataToPush, isValidConnector, - onEditClick, }: UsePushToService): ReturnUsePushToService => { const { permissions } = useCasesContext(); const { isLoading, pushCaseToExternalService } = usePostPushToService(); + const refreshCaseViewPage = useRefreshCaseViewPage(); - const { isLoading: loadingLicense, data: actionLicense = null } = useGetActionLicense(); + const { isLoading: isLoadingLicense, data: actionLicense = null } = useGetActionLicense(); const hasLicenseError = actionLicense != null && !actionLicense.enabledInLicense; - const refreshCaseViewPage = useRefreshCaseViewPage(); + const needsToBePushed = !!caseConnectors[connector.id]?.push.needsToBePushed; + const hasBeenPushed = !!caseConnectors[connector.id]?.push.hasBeenPushed; const handlePushToService = useCallback(async () => { if (connector.id != null && connector.id !== 'none') { @@ -88,7 +87,7 @@ export const usePushToService = ({ * By priority of importance: * 1. Show license error. * 2. Show configuration error. - * 3. Show connector configuration error if the connector is set to none or no connectors have been created. + * 3. Show connector missing information if the connector is set to none. * 4. Show an error message if the connector has been deleted or the user does not have access to it. * 5. Show case closed message. */ @@ -101,11 +100,11 @@ export const usePushToService = ({ return [getKibanaConfigError()]; } - if (connector.id === 'none' && !loadingLicense && !hasLicenseError) { + if (connector.id === 'none' && !isLoadingLicense && !hasLicenseError) { return [getConnectorMissingInfo()]; } - if (!isValidConnector && !loadingLicense && !hasLicenseError) { + if (!isValidConnector && !isLoadingLicense && !hasLicenseError) { return [getDeletedConnectorError()]; } @@ -114,79 +113,24 @@ export const usePushToService = ({ } return errors; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionLicense, caseStatus, connectors.length, connector, loadingLicense, permissions.update]); - - const pushToServiceButton = useMemo( - () => ( - 0 || - !permissions.push || - !isValidConnector || - !hasDataToPush - } - isLoading={isLoading} - > - {caseServices[connector.id] - ? i18n.UPDATE_THIRD(connector.name) - : i18n.PUSH_THIRD(connector.name)} - - ), - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - connector, - connectors, - errorsMsg, - handlePushToService, - hasDataToPush, - isLoading, - loadingLicense, - permissions.push, - isValidConnector, - ] - ); - - const objToReturn = useMemo(() => { - const hidePushButton = errorsMsg.length > 0 || !hasDataToPush || !permissions.push; - - return { - pushButton: hidePushButton ? ( - 0 ? errorsMsg[0].title : i18n.PUSH_LOCKED_TITLE(connector.name)} - content={

      {errorsMsg.length > 0 ? errorsMsg[0].description : i18n.PUSH_LOCKED_DESC}

      } - > - {pushToServiceButton} -
      - ) : ( - <>{pushToServiceButton} - ), - pushCallouts: - errorsMsg.length > 0 ? ( - 0} - hasLicenseError={hasLicenseError} - messages={errorsMsg} - onEditClick={onEditClick} - /> - ) : null, - }; }, [ - connector.name, - connectors.length, - errorsMsg, - hasDataToPush, + actionLicense, + caseStatus, + connector.id, hasLicenseError, - onEditClick, - pushToServiceButton, - permissions.push, + isValidConnector, + isLoadingLicense, + permissions.update, ]); - return objToReturn; + return { + errorsMsg, + hasErrorMessages: errorsMsg.length > 0, + needsToBePushed, + hasBeenPushed, + isLoading: isLoading || isLoadingLicense, + hasPushPermissions: permissions.push, + hasLicenseError, + handlePushToService, + }; }; diff --git a/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts b/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts index 1214b9f790e0a..0f7e98a3845f2 100644 --- a/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts +++ b/x-pack/plugins/cases/public/components/use_push_to_service/translations.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export * from '../../common/translations'; -export const PUSH_THIRD = (thirdParty: string) => { +export const PUSH_INCIDENT = (thirdParty: string) => { if (thirdParty === 'none') { return i18n.translate('xpack.cases.caseView.pushThirdPartyIncident', { defaultMessage: 'Push as external incident', @@ -22,7 +22,7 @@ export const PUSH_THIRD = (thirdParty: string) => { }); }; -export const UPDATE_THIRD = (thirdParty: string) => { +export const UPDATE_INCIDENT = (thirdParty: string) => { if (thirdParty === 'none') { return i18n.translate('xpack.cases.caseView.updateThirdPartyIncident', { defaultMessage: 'Update external incident', @@ -76,3 +76,10 @@ export const PUSH_DISABLE_BY_LICENSE_TITLE = i18n.translate( export const LINK_CLOUD_DEPLOYMENT = i18n.translate('xpack.cases.caseView.cloudDeploymentLink', { defaultMessage: 'cloud deployment', }); + +export const LINK_ACTIONS_CONFIGURATION = i18n.translate( + 'xpack.cases.caseView.actionsConfigurationLink', + { + defaultMessage: 'Alerting and action settings in Kibana', + } +); 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 f5ec9ec742d55..c63f53f13bbe6 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 @@ -86,7 +86,7 @@ const getCreateCommentUserAction = ({ comment: Comment; } & Omit< UserActionBuilderArgs, - 'caseServices' | 'comments' | 'index' | 'handleOutlineComment' | 'currentUserProfile' + 'comments' | 'index' | 'handleOutlineComment' | 'currentUserProfile' >): EuiCommentProps[] => { switch (comment.type) { case CommentType.user: @@ -185,6 +185,7 @@ export const createCommentUserActionBuilder: UserActionBuilder = ({ handleManageQuote, handleOutlineComment, actionsNavigation, + caseConnectors, }) => ({ build: () => { const commentUserAction = userAction as UserActionResponse; @@ -226,6 +227,7 @@ export const createCommentUserActionBuilder: UserActionBuilder = ({ handleDeleteComment, handleManageQuote, actionsNavigation, + caseConnectors, }); return commentAction; 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 4ba2143254065..2cfc535940f45 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 @@ -14,7 +14,6 @@ import routeData from 'react-router'; import { useUpdateComment } from '../../containers/use_update_comment'; import { basicCase, - basicPush, getUserAction, getHostIsolationUserAction, hostIsolationComment, @@ -24,6 +23,7 @@ import type { AppMockRenderer } from '../../common/mock'; import { createAppMockRenderer, TestProviders } from '../../common/mock'; import { Actions } from '../../../common/api'; import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock'; +import { connectorsMock, getCaseConnectorsMockResponse } from '../../common/mock/connectors'; const fetchUserActions = jest.fn(); const onUpdateField = jest.fn(); @@ -31,11 +31,11 @@ const updateCase = jest.fn(); const onShowAlertDetails = jest.fn(); const defaultProps = { - caseServices: {}, + caseConnectors: getCaseConnectorsMockResponse(), caseUserActions: [], userProfiles: new Map(), currentUserProfile: undefined, - connectors: [], + connectors: connectorsMock, actionsNavigation: { href: jest.fn(), onClick: jest.fn() }, getRuleDetailsHref: jest.fn(), onRuleDetailsClick: jest.fn(), @@ -95,29 +95,26 @@ describe(`UserActions`, () => { }); it('Renders service now update line with top and bottom when push is required', async () => { + const caseConnectors = getCaseConnectorsMockResponse({ 'push.needsToBePushed': true }); + const ourActions = [ - getUserAction('pushed', 'push_to_service'), - getUserAction('comment', Actions.update), + getUserAction('pushed', 'push_to_service', { + createdAt: '2023-01-17T09:46:29.813Z', + }), ]; const props = { ...defaultProps, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 0, - lastPushIndex: 0, - commentsToUpdate: [`${ourActions[ourActions.length - 1].commentId}`], - hasDataToPush: true, - }, - }, + caseConnectors, caseUserActions: ourActions, }; + const wrapper = mount( ); + await waitFor(() => { expect(wrapper.find(`[data-test-subj="top-footer"]`).exists()).toEqual(true); expect(wrapper.find(`[data-test-subj="bottom-footer"]`).exists()).toEqual(true); @@ -125,19 +122,15 @@ describe(`UserActions`, () => { }); it('Renders service now update line with top only when push is up to date', async () => { - const ourActions = [getUserAction('pushed', 'push_to_service')]; + const ourActions = [ + getUserAction('pushed', 'push_to_service', { + createdAt: '2023-01-17T09:46:29.813Z', + }), + ]; + const props = { ...defaultProps, caseUserActions: ourActions, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 0, - lastPushIndex: 0, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, }; const wrapper = mount( @@ -150,6 +143,7 @@ describe(`UserActions`, () => { expect(wrapper.find(`[data-test-subj="bottom-footer"]`).exists()).toEqual(false); }); }); + it('Outlines comment when update move to link is clicked', async () => { const ourActions = [ getUserAction('comment', Actions.create), 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 bf6aaf6ea2a3a..74c5cca881c2d 100644 --- a/x-pack/plugins/cases/public/components/user_actions/index.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/index.tsx @@ -80,7 +80,7 @@ const MyEuiCommentList = styled(EuiCommentList)` export const UserActions = React.memo( ({ - caseServices, + caseConnectors, caseUserActions, userProfiles, currentUserProfile, @@ -190,12 +190,12 @@ export const UserActions = React.memo( const userActionBuilder = builder({ appId, caseData, + caseConnectors, externalReferenceAttachmentTypeRegistry, persistableStateAttachmentTypeRegistry, userAction, userProfiles, currentUserProfile, - caseServices, comments: caseData.comments, index, commentRefs, @@ -220,6 +220,7 @@ export const UserActions = React.memo( ), [ appId, + caseConnectors, caseUserActions, userProfiles, currentUserProfile, @@ -227,7 +228,6 @@ export const UserActions = React.memo( persistableStateAttachmentTypeRegistry, descriptionCommentListObj, caseData, - caseServices, commentRefs, manageMarkdownEditIds, selectedOutlineCommentId, 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 33eae2b43fbae..87034e4ccf191 100644 --- a/x-pack/plugins/cases/public/components/user_actions/mock.ts +++ b/x-pack/plugins/cases/public/components/user_actions/mock.ts @@ -9,22 +9,14 @@ import { Actions } from '../../../common/api'; import { SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { ExternalReferenceAttachmentTypeRegistry } from '../../client/attachment_framework/external_reference_registry'; import { PersistableStateAttachmentTypeRegistry } from '../../client/attachment_framework/persistable_state_registry'; -import { basicCase, basicPush, getUserAction } from '../../containers/mock'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; +import { basicCase, getUserAction } from '../../containers/mock'; import { userProfiles, userProfilesMap } from '../../containers/user_profiles/api.mock'; import type { UserActionBuilderArgs } from './types'; export const getMockBuilderArgs = (): UserActionBuilderArgs => { const userAction = getUserAction('title', Actions.update); const commentRefs = { current: {} }; - const caseServices = { - '123': { - ...basicPush, - firstPushIndex: 0, - lastPushIndex: 0, - commentsToUpdate: [], - hasDataToPush: true, - }, - }; const alertData = { 'alert-id-1': { @@ -51,6 +43,8 @@ export const getMockBuilderArgs = (): UserActionBuilderArgs => { }, }; + const caseConnectors = getCaseConnectorsMockResponse(); + const getRuleDetailsHref = jest.fn().mockReturnValue('https://example.com'); const onRuleDetailsClick = jest.fn(); const onShowAlertDetails = jest.fn(); @@ -70,7 +64,6 @@ export const getMockBuilderArgs = (): UserActionBuilderArgs => { persistableStateAttachmentTypeRegistry, caseData: basicCase, comments: basicCase.comments, - caseServices, index: 0, alertData, commentRefs, @@ -78,6 +71,7 @@ export const getMockBuilderArgs = (): UserActionBuilderArgs => { selectedOutlineCommentId: '', loadingCommentIds: [], loadingAlertData: false, + caseConnectors, getRuleDetailsHref, onRuleDetailsClick, onShowAlertDetails, diff --git a/x-pack/plugins/cases/public/components/user_actions/pushed.test.tsx b/x-pack/plugins/cases/public/components/user_actions/pushed.test.tsx index 219a7a6d2c7c8..14e7199047a7a 100644 --- a/x-pack/plugins/cases/public/components/user_actions/pushed.test.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/pushed.test.tsx @@ -14,13 +14,13 @@ import { getUserAction } from '../../containers/mock'; import { TestProviders } from '../../common/mock'; import { createPushedUserActionBuilder } from './pushed'; import { getMockBuilderArgs } from './mock'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; jest.mock('../../common/lib/kibana'); jest.mock('../../common/navigation/hooks'); describe('createPushedUserActionBuilder ', () => { const builderArgs = getMockBuilderArgs(); - const caseServices = builderArgs.caseServices; beforeEach(() => { jest.clearAllMocks(); @@ -31,8 +31,6 @@ describe('createPushedUserActionBuilder ', () => { const builder = createPushedUserActionBuilder({ ...builderArgs, userAction, - caseServices, - index: 0, }); const createdUserAction = builder.build(); @@ -42,20 +40,22 @@ describe('createPushedUserActionBuilder ', () => { ); - expect(screen.getByText('pushed as new incident connector name')).toBeInTheDocument(); + expect(screen.getByText('pushed as new incident My SN connector')).toBeInTheDocument(); expect(screen.getByText('external title').closest('a')).toHaveAttribute( 'href', 'basicPush.com' ); }); - it('renders correctly when updating an external service', async () => { + it('renders correctly if oldestUserActionPushDate is not defined', async () => { const userAction = getUserAction('pushed', Actions.push_to_service); + const caseConnectors = getCaseConnectorsMockResponse({ + 'push.details.oldestUserActionPushDate': undefined, + }); const builder = createPushedUserActionBuilder({ ...builderArgs, + caseConnectors, userAction, - caseServices, - index: 1, }); const createdUserAction = builder.build(); @@ -65,22 +65,19 @@ describe('createPushedUserActionBuilder ', () => { ); - expect(screen.getByText('updated incident connector name')).toBeInTheDocument(); + expect(screen.getByText('pushed as new incident My SN connector')).toBeInTheDocument(); }); - it('renders the pushing indicators correctly', async () => { + it('renders correctly when updating an external service', async () => { const userAction = getUserAction('pushed', Actions.push_to_service); + const caseConnectors = getCaseConnectorsMockResponse({ + 'push.details.oldestUserActionPushDate': '2023-01-16T09:46:29.813Z', + }); + const builder = createPushedUserActionBuilder({ ...builderArgs, + caseConnectors, userAction, - caseServices: { - ...caseServices, - '123': { - ...caseServices['123'], - lastPushIndex: 1, - }, - }, - index: 1, }); const createdUserAction = builder.build(); @@ -90,24 +87,65 @@ describe('createPushedUserActionBuilder ', () => { ); - expect(screen.getByText('Already pushed to connector name incident')).toBeInTheDocument(); - expect(screen.getByText('Requires update to connector name incident')).toBeInTheDocument(); + expect(screen.getByText('updated incident My SN connector')).toBeInTheDocument(); }); - it('shows only the already pushed indicator if has no data to push', async () => { - const userAction = getUserAction('pushed', Actions.push_to_service); + it('shows only the top footer if it is the latest push and there is nothing to push', async () => { + const userAction = getUserAction('pushed', Actions.push_to_service, { + createdAt: '2023-01-17T09:46:29.813Z', + }); + + const builder = createPushedUserActionBuilder({ + ...builderArgs, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('Already pushed to My SN connector incident')).toBeInTheDocument(); + expect( + screen.queryByText('Requires update to My SN connector incident') + ).not.toBeInTheDocument(); + }); + + it('shows both footers if the connectors needs to be pushed and is the latest push', async () => { + const caseConnectors = getCaseConnectorsMockResponse({ + 'push.needsToBePushed': true, + }); + + const userAction = getUserAction('pushed', Actions.push_to_service, { + createdAt: '2023-01-17T09:46:29.813Z', + }); + const builder = createPushedUserActionBuilder({ + ...builderArgs, + caseConnectors, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect(screen.getByText('Already pushed to My SN connector incident')).toBeInTheDocument(); + expect(screen.getByText('Requires update to My SN connector incident')).toBeInTheDocument(); + }); + + it('does not show the footers if it is not the latest push', async () => { + const userAction = getUserAction('pushed', Actions.push_to_service, { + createdAt: '2020-01-17T09:46:29.813Z', + }); + const builder = createPushedUserActionBuilder({ ...builderArgs, userAction, - caseServices: { - ...caseServices, - '123': { - ...caseServices['123'], - lastPushIndex: 1, - hasDataToPush: false, - }, - }, - index: 1, }); const createdUserAction = builder.build(); @@ -117,14 +155,51 @@ describe('createPushedUserActionBuilder ', () => { ); - expect(screen.getByText('Already pushed to connector name incident')).toBeInTheDocument(); expect( - screen.queryByText('Requires update to connector name incident') + screen.queryByText('Already pushed to My SN connector incident') + ).not.toBeInTheDocument(); + + expect( + screen.queryByText('Requires update to My SN connector incident') + ).not.toBeInTheDocument(); + }); + + it('does not show the footers if latestUserActionPushDate is not defined', async () => { + const caseConnectors = getCaseConnectorsMockResponse({ + 'push.needsToBePushed': true, + 'push.details.latestUserActionPushDate': undefined, + }); + + const userAction = getUserAction('pushed', Actions.push_to_service, { + createdAt: '2023-01-17T09:46:29.813Z', + }); + + const builder = createPushedUserActionBuilder({ + ...builderArgs, + caseConnectors, + userAction, + }); + + const createdUserAction = builder.build(); + render( + + + + ); + + expect( + screen.queryByText('Already pushed to My SN connector incident') + ).not.toBeInTheDocument(); + + expect( + screen.queryByText('Requires update to My SN connector incident') ).not.toBeInTheDocument(); }); it('does not show the push information if the connector is none', async () => { + const caseConnectors = getCaseConnectorsMockResponse({ 'push.needsToBePushed': true }); const userAction = getUserAction('pushed', Actions.push_to_service, { + createdAt: '2023-01-17T09:46:29.813Z', payload: { externalService: { connectorId: NONE_CONNECTOR_ID, connectorName: 'none connector' }, }, @@ -132,15 +207,8 @@ describe('createPushedUserActionBuilder ', () => { const builder = createPushedUserActionBuilder({ ...builderArgs, + caseConnectors, userAction, - caseServices: { - ...caseServices, - '123': { - ...caseServices['123'], - lastPushIndex: 1, - }, - }, - index: 1, }); const createdUserAction = builder.build(); @@ -152,9 +220,11 @@ describe('createPushedUserActionBuilder ', () => { expect(screen.queryByText('pushed as new incident none connector')).not.toBeInTheDocument(); expect(screen.queryByText('updated incident none connector')).not.toBeInTheDocument(); - expect(screen.queryByText('Already pushed to connector name incident')).not.toBeInTheDocument(); expect( - screen.queryByText('Requires update to connector name incident') + screen.queryByText('Already pushed to My SN connector incident') + ).not.toBeInTheDocument(); + expect( + screen.queryByText('Requires update to My SN connector incident') ).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/cases/public/components/user_actions/pushed.tsx b/x-pack/plugins/cases/public/components/user_actions/pushed.tsx index e0352d7d96ccb..6386cade1eadd 100644 --- a/x-pack/plugins/cases/public/components/user_actions/pushed.tsx +++ b/x-pack/plugins/cases/public/components/user_actions/pushed.tsx @@ -10,29 +10,52 @@ import type { EuiCommentProps } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import type { PushedUserAction } from '../../../common/api'; -import { Actions, NONE_CONNECTOR_ID } from '../../../common/api'; +import { Actions } from '../../../common/api'; import type { UserActionBuilder, UserActionResponse } from './types'; import { createCommonUpdateUserActionBuilder } from './common'; import * as i18n from './translations'; -import type { CaseServices } from '../../containers/use_find_case_user_actions'; -import type { CaseExternalService } from '../../containers/types'; - -const getPushInfo = ( - caseServices: CaseServices, - externalService: CaseExternalService | undefined, - index: number -) => - externalService != null && externalService.connectorId !== NONE_CONNECTOR_ID - ? { - firstPush: caseServices[externalService.connectorId]?.firstPushIndex === index, - parsedConnectorId: externalService.connectorId, - parsedConnectorName: externalService.connectorName, - } - : { - firstPush: false, - parsedConnectorId: NONE_CONNECTOR_ID, - parsedConnectorName: NONE_CONNECTOR_ID, - }; +import type { CaseConnectors } from '../../containers/types'; + +const getPushDates = ( + userActionPushedAt: string, + connectorPushedAt: string | undefined +): { userActionDate: Date; connectorDate: Date } | undefined => { + if (!connectorPushedAt) { + return; + } + + const pushedDate = new Date(userActionPushedAt); + const connectorDate = new Date(connectorPushedAt); + + if (isNaN(pushedDate.getTime()) || isNaN(connectorDate.getTime())) { + return; + } + + return { + userActionDate: pushedDate, + connectorDate, + }; +}; + +const isLatestPush = (pushedAt: string, latestPush: string | undefined) => { + const dates = getPushDates(pushedAt, latestPush); + + if (!dates) { + return false; + } + + return dates.userActionDate.getTime() >= dates.connectorDate.getTime(); +}; + +const isFirstPush = (pushedAt: string, oldestPush: string | undefined) => { + const dates = getPushDates(pushedAt, oldestPush); + + if (!dates) { + return true; + } + + return dates.userActionDate.getTime() <= dates.connectorDate.getTime(); +}; const getLabelTitle = (action: UserActionResponse, firstPush: boolean) => { const externalService = action.payload.externalService; @@ -60,50 +83,36 @@ const getLabelTitle = (action: UserActionResponse, firstPush: const getFooters = ({ userAction, - caseServices, - connectorId, - connectorName, - index, + connectorInfo, }: { userAction: UserActionResponse; - caseServices: CaseServices; - connectorId: string; - connectorName: string; - index: number; + connectorInfo: CaseConnectors[string]; }): EuiCommentProps[] => { - const showTopFooter = - userAction.action === Actions.push_to_service && - index === caseServices[connectorId]?.lastPushIndex; - - const showBottomFooter = - userAction.action === Actions.push_to_service && - index === caseServices[connectorId]?.lastPushIndex && - caseServices[connectorId].hasDataToPush; + const footers: EuiCommentProps[] = []; + const latestPush = isLatestPush( + userAction.createdAt, + connectorInfo.push.details?.latestUserActionPushDate + ); - let footers: EuiCommentProps[] = []; + const showTopFooter = userAction.action === Actions.push_to_service && latestPush; + const showBottomFooter = showTopFooter && connectorInfo.push.needsToBePushed; if (showTopFooter) { - footers = [ - ...footers, - { - username: '', - event: i18n.ALREADY_PUSHED_TO_SERVICE(`${connectorName}`), - timelineAvatar: 'sortUp', - 'data-test-subj': 'top-footer', - }, - ]; + footers.push({ + username: '', + event: i18n.ALREADY_PUSHED_TO_SERVICE(`${connectorInfo.name}`), + timelineAvatar: 'sortUp', + 'data-test-subj': 'top-footer', + }); } if (showBottomFooter) { - footers = [ - ...footers, - { - username: '', - event: i18n.REQUIRED_UPDATE_TO_SERVICE(`${connectorName}`), - timelineAvatar: 'sortDown', - 'data-test-subj': 'bottom-footer', - }, - ]; + footers.push({ + username: '', + event: i18n.REQUIRED_UPDATE_TO_SERVICE(`${connectorInfo.name}`), + timelineAvatar: 'sortDown', + 'data-test-subj': 'bottom-footer', + }); } return footers; @@ -112,31 +121,25 @@ const getFooters = ({ export const createPushedUserActionBuilder: UserActionBuilder = ({ userAction, userProfiles, - caseServices, - index, + caseConnectors, handleOutlineComment, }) => ({ build: () => { const pushedUserAction = userAction as UserActionResponse; - const { firstPush, parsedConnectorId, parsedConnectorName } = getPushInfo( - caseServices, - pushedUserAction.payload.externalService, - index - ); + const connectorId = pushedUserAction.payload.externalService.connectorId; + const connectorInfo = caseConnectors[connectorId]; - if (parsedConnectorId === NONE_CONNECTOR_ID) { + if (!connectorInfo) { return []; } - const footers = getFooters({ - userAction: pushedUserAction, - caseServices, - connectorId: parsedConnectorId, - connectorName: parsedConnectorName, - index, - }); - + const firstPush = isFirstPush( + userAction.createdAt, + connectorInfo.push.details?.oldestUserActionPushDate + ); + const footers = getFooters({ userAction: pushedUserAction, connectorInfo }); const label = getLabelTitle(pushedUserAction, firstPush); + const commonBuilder = createCommonUpdateUserActionBuilder({ userProfiles, userAction, 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 9bf750437d29d..92001d633e912 100644 --- a/x-pack/plugins/cases/public/components/user_actions/types.ts +++ b/x-pack/plugins/cases/public/components/user_actions/types.ts @@ -9,8 +9,13 @@ import type { EuiCommentProps } from '@elastic/eui'; import type { UserProfileWithAvatar } from '@kbn/user-profile-components'; import type { SnakeToCamelCase } from '../../../common/types'; import type { ActionTypes, UserActionWithResponse } from '../../../common/api'; -import type { Case, CaseUserActions, Comment, UseFetchAlertData } from '../../containers/types'; -import type { CaseServices } from '../../containers/use_find_case_user_actions'; +import type { + Case, + CaseConnectors, + CaseUserActions, + Comment, + UseFetchAlertData, +} from '../../containers/types'; import type { AddCommentRefObject } from '../add_comment'; import type { UserActionMarkdownRefObject } from './markdown_form'; import type { CasesNavigation } from '../links'; @@ -21,7 +26,7 @@ import type { PersistableStateAttachmentTypeRegistry } from '../../client/attach import type { CurrentUserProfile } from '../types'; export interface UserActionTreeProps { - caseServices: CaseServices; + caseConnectors: CaseConnectors; caseUserActions: CaseUserActions[]; userProfiles: Map; currentUserProfile: CurrentUserProfile; @@ -47,8 +52,8 @@ export interface UserActionBuilderArgs { currentUserProfile: CurrentUserProfile; externalReferenceAttachmentTypeRegistry: ExternalReferenceAttachmentTypeRegistry; persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry; + caseConnectors: CaseConnectors; userAction: CaseUserActions; - caseServices: CaseServices; comments: Comment[]; index: number; commentRefs: React.MutableRefObject< diff --git a/x-pack/plugins/cases/public/containers/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/__mocks__/api.ts index 431ce6626e2ea..ea329f67ad791 100644 --- a/x-pack/plugins/cases/public/containers/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/__mocks__/api.ts @@ -27,7 +27,7 @@ import { tags, findCaseUserActionsResponse, } from '../mock'; -import type { CaseUpdateRequest, ResolvedCase } from '../../../common/ui/types'; +import type { CaseConnectors, CaseUpdateRequest, ResolvedCase } from '../../../common/ui/types'; import { SeverityAll } from '../../../common/ui/types'; import type { CasePatchRequest, @@ -39,6 +39,7 @@ import { CaseStatuses } from '../../../common/api'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; import type { UserProfile } from '@kbn/security-plugin/common'; import { userProfiles } from '../user_profiles/api.mock'; +import { getCaseConnectorsMockResponse } from '../../common/mock/connectors'; export const getCase = async ( caseId: string, @@ -140,3 +141,8 @@ export const getFeatureIds = async ( _query: { registrationContext: string[] }, _signal: AbortSignal ): Promise => Promise.resolve(['siem', 'observability']); + +export const getCaseConnectors = async ( + caseId: string, + signal: AbortSignal +): Promise => Promise.resolve(getCaseConnectorsMockResponse()); diff --git a/x-pack/plugins/cases/public/containers/api.test.tsx b/x-pack/plugins/cases/public/containers/api.test.tsx index c744fc36335cc..2f0c8645b3317 100644 --- a/x-pack/plugins/cases/public/containers/api.test.tsx +++ b/x-pack/plugins/cases/public/containers/api.test.tsx @@ -11,6 +11,7 @@ import { KibanaServices } from '../common/lib/kibana'; import { ConnectorTypes, CommentType, CaseStatuses, CaseSeverity } from '../../common/api'; import { + CASES_INTERNAL_URL, CASES_URL, INTERNAL_BULK_CREATE_ATTACHMENTS_URL, SECURITY_SOLUTION_OWNER, @@ -34,6 +35,7 @@ import { resolveCase, getFeatureIds, postComment, + getCaseConnectors, } from './api'; import { @@ -54,10 +56,13 @@ import { caseWithRegisteredAttachmentsSnake, caseWithRegisteredAttachments, caseUserActionsWithRegisteredAttachmentsSnake, + basicPushSnake, } from './mock'; import { DEFAULT_FILTER_OPTIONS, DEFAULT_QUERY_PARAMS } from './use_get_cases'; import { getCasesStatus } from '../api'; +import { getCaseConnectorsMockResponse } from '../common/mock/connectors'; +import { cloneDeep, set } from 'lodash'; const abortCtrl = new AbortController(); const mockKibanaServices = KibanaServices.get as jest.Mock; @@ -846,4 +851,31 @@ describe('Cases API', () => { expect(resp).toEqual(caseWithRegisteredAttachments); }); }); + + describe('getCaseConnectors', () => { + const caseConnectors = getCaseConnectorsMockResponse(); + const connectorCamelCase = caseConnectors['servicenow-1']; + + const snakeCaseConnector = cloneDeep(connectorCamelCase); + set(snakeCaseConnector, 'push.details.externalService', basicPushSnake); + + beforeEach(() => { + fetchMock.mockClear(); + fetchMock.mockResolvedValue({ 'servicenow-1': snakeCaseConnector }); + }); + + it('should be called with correct check url, method, signal', async () => { + await getCaseConnectors(basicCase.id, abortCtrl.signal); + + expect(fetchMock).toHaveBeenCalledWith(`${CASES_INTERNAL_URL}/${basicCase.id}/_connectors`, { + method: 'GET', + signal: abortCtrl.signal, + }); + }); + + it('should return correct response', async () => { + const resp = await getCaseConnectors(basicCase.id, abortCtrl.signal); + expect(resp).toEqual({ 'servicenow-1': connectorCamelCase }); + }); + }); }); diff --git a/x-pack/plugins/cases/public/containers/api.ts b/x-pack/plugins/cases/public/containers/api.ts index 9c4f1a07ee62f..aabe48638efa7 100644 --- a/x-pack/plugins/cases/public/containers/api.ts +++ b/x-pack/plugins/cases/public/containers/api.ts @@ -8,6 +8,7 @@ import type { ValidFeatureId } from '@kbn/rule-data-utils'; import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common/constants'; import type { + CaseConnectors, Cases, CaseUpdateRequest, FetchCasesProps, @@ -27,6 +28,7 @@ import type { User, SingleCaseMetricsResponse, CasesFindResponse, + GetCaseConnectorsResponse, } from '../../common/api'; import { CommentType, @@ -36,6 +38,7 @@ import { getCasePushUrl, getCaseFindUserActionsUrl, getCaseCommentDeleteUrl, + getCaseConnectorsUrl, } from '../../common/api'; import { CASE_REPORTERS_URL, @@ -378,3 +381,28 @@ export const getFeatureIds = async ( } ); }; + +export const getCaseConnectors = async ( + caseId: string, + signal: AbortSignal +): Promise => { + const res = await KibanaServices.get().http.fetch( + getCaseConnectorsUrl(caseId), + { + method: 'GET', + signal, + } + ); + + return Object.keys(res).reduce( + (acc, connectorId) => ({ + ...acc, + [connectorId]: { + ...convertToCamelCase( + res[connectorId] + ), + }, + }), + {} + ); +}; diff --git a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts index 0d85e75478be3..607f6d01191ff 100644 --- a/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/__mocks__/api.ts @@ -17,8 +17,9 @@ import type { CaseConfigure } from '../types'; import { caseConfigurationCamelCaseResponseMock } from '../mock'; import { actionTypesMock, connectorsMock } from '../../../common/mock/connectors'; -export const fetchConnectors = async ({ signal }: ApiProps): Promise => - Promise.resolve(connectorsMock); +export const getSupportedActionConnectors = async ({ + signal, +}: ApiProps): Promise => Promise.resolve(connectorsMock); export const getCaseConfigure = async ({ signal }: ApiProps): Promise => Promise.resolve(caseConfigurationCamelCaseResponseMock); diff --git a/x-pack/plugins/cases/public/containers/configure/api.test.ts b/x-pack/plugins/cases/public/containers/configure/api.test.ts index 0b1f0c8d172ea..9099f908a7871 100644 --- a/x-pack/plugins/cases/public/containers/configure/api.test.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.test.ts @@ -6,7 +6,7 @@ */ import { - fetchConnectors, + getSupportedActionConnectors, getCaseConfigure, postCaseConfigure, patchCaseConfigure, @@ -37,7 +37,7 @@ describe('Case Configuration API', () => { }); test('check url, method, signal', async () => { - await fetchConnectors({ signal: abortCtrl.signal }); + await getSupportedActionConnectors({ signal: abortCtrl.signal }); expect(fetchMock).toHaveBeenCalledWith('/api/cases/configure/connectors/_find', { method: 'GET', signal: abortCtrl.signal, @@ -45,7 +45,7 @@ describe('Case Configuration API', () => { }); test('happy path', async () => { - const resp = await fetchConnectors({ signal: abortCtrl.signal }); + const resp = await getSupportedActionConnectors({ signal: abortCtrl.signal }); expect(resp).toEqual(connectorsMock); }); }); diff --git a/x-pack/plugins/cases/public/containers/configure/api.ts b/x-pack/plugins/cases/public/containers/configure/api.ts index 72702e27fbb56..b0f2b8df4a093 100644 --- a/x-pack/plugins/cases/public/containers/configure/api.ts +++ b/x-pack/plugins/cases/public/containers/configure/api.ts @@ -24,7 +24,9 @@ import type { ApiProps } from '../types'; import { decodeCaseConfigurationsResponse, decodeCaseConfigureResponse } from '../utils'; import type { CaseConfigure } from './types'; -export const fetchConnectors = async ({ signal }: ApiProps): Promise => { +export const getSupportedActionConnectors = async ({ + signal, +}: ApiProps): Promise => { const response = await KibanaServices.get().http.fetch( `${CASE_CONFIGURE_CONNECTORS_URL}/_find`, { method: 'GET', signal } diff --git a/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx similarity index 85% rename from x-pack/plugins/cases/public/containers/configure/use_connectors.tsx rename to x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx index f9c19ee1776bd..0bba69ca47df1 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_connectors.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx @@ -6,13 +6,13 @@ */ import { useQuery } from '@tanstack/react-query'; -import { fetchConnectors } from './api'; +import { getSupportedActionConnectors } from './api'; import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana'; import * as i18n from './translations'; import { casesQueriesKeys } from '../constants'; import type { ServerError } from '../../types'; -export function useGetConnectors() { +export function useGetSupportedActionConnectors() { const toasts = useToasts(); const { actions } = useApplicationCapabilities(); return useQuery( @@ -22,7 +22,7 @@ export function useGetConnectors() { return []; } const abortCtrl = new AbortController(); - return fetchConnectors({ signal: abortCtrl.signal }); + return getSupportedActionConnectors({ signal: abortCtrl.signal }); }, { onError: (error: ServerError) => { diff --git a/x-pack/plugins/cases/public/containers/configure/use_connectors.test.tsx b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx similarity index 78% rename from x-pack/plugins/cases/public/containers/configure/use_connectors.test.tsx rename to x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx index 076e1a8408482..36cbd9417e375 100644 --- a/x-pack/plugins/cases/public/containers/configure/use_connectors.test.tsx +++ b/x-pack/plugins/cases/public/containers/configure/use_get_supported_action_connectors.tsx.test.tsx @@ -10,7 +10,7 @@ import { renderHook } from '@testing-library/react-hooks'; import * as api from './api'; import { TestProviders } from '../../common/mock'; import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana'; -import { useGetConnectors } from './use_connectors'; +import { useGetSupportedActionConnectors } from './use_get_supported_action_connectors'; const useApplicationCapabilitiesMock = useApplicationCapabilities as jest.Mocked< typeof useApplicationCapabilities @@ -25,8 +25,8 @@ describe('useConnectors', () => { }); it('fetches connectors', async () => { - const spy = jest.spyOn(api, 'fetchConnectors'); - const { waitForNextUpdate } = renderHook(() => useGetConnectors(), { + const spy = jest.spyOn(api, 'getSupportedActionConnectors'); + const { waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }) => {children}, }); @@ -39,12 +39,12 @@ describe('useConnectors', () => { const addError = jest.fn(); (useToasts as jest.Mock).mockReturnValue({ addError }); - const spyOnfetchConnectors = jest.spyOn(api, 'fetchConnectors'); + const spyOnfetchConnectors = jest.spyOn(api, 'getSupportedActionConnectors'); spyOnfetchConnectors.mockImplementation(() => { throw new Error('Something went wrong'); }); - const { waitForNextUpdate } = renderHook(() => useGetConnectors(), { + const { waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }) => {children}, }); await waitForNextUpdate(); @@ -53,10 +53,10 @@ describe('useConnectors', () => { }); it('does not fetch connectors when the user does not has access to actions', async () => { - const spyOnFetchConnectors = jest.spyOn(api, 'fetchConnectors'); + const spyOnFetchConnectors = jest.spyOn(api, 'getSupportedActionConnectors'); useApplicationCapabilitiesMock().actions = { crud: false, read: false }; - const { result, waitForNextUpdate } = renderHook(() => useGetConnectors(), { + const { result, waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), { wrapper: ({ children }) => {children}, }); diff --git a/x-pack/plugins/cases/public/containers/constants.ts b/x-pack/plugins/cases/public/containers/constants.ts index 2e57a17be08ec..10d890629311e 100644 --- a/x-pack/plugins/cases/public/containers/constants.ts +++ b/x-pack/plugins/cases/public/containers/constants.ts @@ -24,8 +24,8 @@ export const casesQueriesKeys = { case: (id: string) => [...casesQueriesKeys.caseView(), id] as const, caseMetrics: (id: string, features: SingleCaseMetricsFeature[]) => [...casesQueriesKeys.case(id), 'metrics', features] as const, - userActions: (id: string, connectorId: string) => - [...casesQueriesKeys.case(id), 'user-actions', connectorId] as const, + caseConnectors: (id: string) => [...casesQueriesKeys.case(id), 'connectors'], + userActions: (id: string) => [...casesQueriesKeys.case(id), 'user-actions'] as const, userProfiles: () => [...casesQueriesKeys.users, 'user-profiles'] as const, userProfilesList: (ids: string[]) => [...casesQueriesKeys.userProfiles(), ids] as const, currentUser: () => [...casesQueriesKeys.users, 'current-user'] as const, diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 5cadec4818457..4abe077f378db 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -53,12 +53,14 @@ export { connectorsMock } from '../common/mock/connectors'; export const basicCaseId = 'basic-case-id'; export const caseWithAlertsId = 'case-with-alerts-id'; export const caseWithAlertsSyncOffId = 'case-with-alerts-syncoff-id'; +export const pushConnectorId = 'servicenow-1'; const basicCommentId = 'basic-comment-id'; const basicCreatedAt = '2020-02-19T23:06:33.798Z'; const basicUpdatedAt = '2020-02-20T15:02:57.995Z'; const basicClosedAt = '2020-02-21T15:02:57.995Z'; -const laterTime = '2020-02-28T15:02:57.995Z'; +const basicPushedAt = '2023-01-17T09:46:29.813Z'; +const laterTime = '2023-01-18T09:46:29.813Z'; export const elasticUser = { fullName: 'Leslie Knope', @@ -370,21 +372,21 @@ export const casesMetrics: CasesMetrics = { }; export const basicPush = { - connectorId: '123', - connectorName: 'connector name', + connectorId: pushConnectorId, + connectorName: 'My SN connector', externalId: 'external_id', externalTitle: 'external title', externalUrl: 'basicPush.com', - pushedAt: basicUpdatedAt, + pushedAt: basicPushedAt, pushedBy: elasticUser, }; export const pushedCase: Case = { ...basicCase, connector: { - id: '123', - name: 'My Connector', - type: ConnectorTypes.jira, + id: pushConnectorId, + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, fields: null, }, externalService: basicPush, @@ -539,10 +541,9 @@ export const casesStatusSnake: CasesStatusResponse = { count_open_cases: 20, }; -export const pushConnectorId = '123'; export const pushSnake = { connector_id: pushConnectorId, - connector_name: 'connector name', + connector_name: 'My SN connector', external_id: 'external_id', external_title: 'external title', external_url: 'basicPush.com', @@ -550,16 +551,16 @@ export const pushSnake = { export const basicPushSnake = { ...pushSnake, - pushed_at: basicUpdatedAt, + pushed_at: basicPushedAt, pushed_by: elasticUserSnake, }; export const pushedCaseSnake = { ...basicCaseSnake, connector: { - id: '123', - name: 'My Connector', - type: ConnectorTypes.jira, + id: pushConnectorId, + name: 'My SN connector', + type: ConnectorTypes.serviceNowITSM, fields: null, }, external_service: { ...basicPushSnake, connector_id: pushConnectorId }, @@ -605,11 +606,11 @@ export const getUserAction = ( const externalService = { connectorId: pushConnectorId, - connectorName: 'connector name', + connectorName: 'My SN connector', externalId: 'external_id', externalTitle: 'external title', externalUrl: 'basicPush.com', - pushedAt: basicUpdatedAt, + pushedAt: basicPushedAt, pushedBy: elasticUser, }; @@ -667,6 +668,7 @@ export const getUserAction = ( case ActionTypes.pushed: return { ...commonProperties, + createdAt: basicPushedAt, type: ActionTypes.pushed, payload: { externalService, diff --git a/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx b/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx index 5b98b71468c6f..4330323d7a8b2 100644 --- a/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx +++ b/x-pack/plugins/cases/public/containers/use_find_case_user_actions.test.tsx @@ -5,24 +5,20 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react-hooks'; import type { UseFindCaseUserActions } from './use_find_case_user_actions'; -import { getPushedInfo, useFindCaseUserActions } from './use_find_case_user_actions'; +import { useFindCaseUserActions } from './use_find_case_user_actions'; import { basicCase, - basicPush, caseUserActions, elasticUser, - getJiraConnector, - getUserAction, - jiraFields, findCaseUserActionsResponse, + getUserAction, } from './mock'; import { Actions } from '../../common/api'; import React from 'react'; import { QueryClientProvider } from '@tanstack/react-query'; import { testQueryClient } from '../common/mock'; -import { waitFor } from '@testing-library/dom'; import * as api from './api'; import { useToasts } from '../common/lib/kibana'; @@ -39,36 +35,33 @@ const wrapper: React.FC = ({ children }) => ( {children} ); -describe('useFindCaseUserActions', () => { +describe('UseFindCaseUserActions', () => { beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); it('returns proper state on findCaseUserActions', async () => { - await act(async () => { - const { result } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), - { wrapper } - ); - await waitFor(() => { - expect(result.current).toEqual( - expect.objectContaining({ - ...initialData, - data: { - caseServices: {}, - caseUserActions: [...findCaseUserActionsResponse.userActions], - hasDataToPush: true, - participants: [elasticUser], - profileUids: new Set(), - }, - isError: false, - isLoading: false, - isFetching: false, - }) - ); - }); - }); + const { result, waitForNextUpdate } = renderHook( + () => useFindCaseUserActions(basicCase.id), + { wrapper } + ); + + await waitForNextUpdate(); + + expect(result.current).toEqual( + expect.objectContaining({ + ...initialData, + data: { + caseUserActions: [...findCaseUserActionsResponse.userActions], + participants: [elasticUser], + profileUids: new Set(), + }, + isError: false, + isLoading: false, + isFetching: false, + }) + ); }); it('shows a toast error when the API returns an error', async () => { @@ -78,10 +71,12 @@ describe('useFindCaseUserActions', () => { (useToasts as jest.Mock).mockReturnValue({ addError }); const { waitForNextUpdate } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), + () => useFindCaseUserActions(basicCase.id), { wrapper } ); + await waitForNextUpdate(); + expect(spy).toHaveBeenCalledWith(basicCase.id, expect.any(AbortSignal)); expect(addError).toHaveBeenCalled(); }); @@ -97,20 +92,18 @@ describe('useFindCaseUserActions', () => { }) ); - await act(async () => { - const { result } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), - { wrapper } - ); + const { result, waitForNextUpdate } = renderHook( + () => useFindCaseUserActions(basicCase.id), + { wrapper } + ); + + await waitForNextUpdate(); - await waitFor(() => { - expect(result.current.data?.profileUids).toMatchInlineSnapshot(` + expect(result.current.data?.profileUids).toMatchInlineSnapshot(` Set { "456", } `); - }); - }); }); it('aggregates the uids from a push', async () => { @@ -127,20 +120,18 @@ describe('useFindCaseUserActions', () => { }) ); - await act(async () => { - const { result } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), - { wrapper } - ); + const { result, waitForNextUpdate } = renderHook( + () => useFindCaseUserActions(basicCase.id), + { wrapper } + ); + + await waitForNextUpdate(); - await waitFor(() => { - expect(result.current.data?.profileUids).toMatchInlineSnapshot(` + expect(result.current.data?.profileUids).toMatchInlineSnapshot(` Set { "123", } `); - }); - }); }); it('aggregates the uids from an assignment add user action', async () => { @@ -153,21 +144,19 @@ describe('useFindCaseUserActions', () => { }) ); - await act(async () => { - const { result } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), - { wrapper } - ); + const { result, waitForNextUpdate } = renderHook( + () => useFindCaseUserActions(basicCase.id), + { wrapper } + ); + + await waitForNextUpdate(); - await waitFor(() => { - expect(result.current.data?.profileUids).toMatchInlineSnapshot(` + expect(result.current.data?.profileUids).toMatchInlineSnapshot(` Set { "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", "u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0", } `); - }); - }); }); it('ignores duplicate uids', async () => { @@ -184,21 +173,19 @@ describe('useFindCaseUserActions', () => { }) ); - await act(async () => { - const { result } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), - { wrapper } - ); + const { result, waitForNextUpdate } = renderHook( + () => useFindCaseUserActions(basicCase.id), + { wrapper } + ); + + await waitForNextUpdate(); - await waitFor(() => { - expect(result.current.data?.profileUids).toMatchInlineSnapshot(` + expect(result.current.data?.profileUids).toMatchInlineSnapshot(` Set { "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", "u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0", } `); - }); - }); }); it('aggregates the uids from an assignment delete user action', async () => { @@ -211,562 +198,19 @@ describe('useFindCaseUserActions', () => { }) ); - await act(async () => { - const { result } = renderHook( - () => useFindCaseUserActions(basicCase.id, basicCase.connector.id), - { wrapper } - ); + const { result, waitForNextUpdate } = renderHook( + () => useFindCaseUserActions(basicCase.id), + { wrapper } + ); + + await waitForNextUpdate(); - await waitFor(() => { - expect(result.current.data?.profileUids).toMatchInlineSnapshot(` + expect(result.current.data?.profileUids).toMatchInlineSnapshot(` Set { "u_J41Oh6L9ki-Vo2tOogS8WRTENzhHurGtRc87NgEAlkc_0", "u_A_tM4n0wPkdiQ9smmd8o0Hr_h61XQfu8aRPh9GMoRoc_0", } `); - }); - }); - }); - }); - - describe('getPushedInfo', () => { - it('Correctly marks first/last index - hasDataToPush: false', () => { - const userActions = [...caseUserActions, getUserAction('pushed', Actions.push_to_service)]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Correctly marks first/last index and comment id - hasDataToPush: true', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.create), - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [userActions[userActions.length - 1].commentId], - hasDataToPush: true, - }, - }, - }); - }); - - it('Correctly marks first/last index and multiple comment ids, both needs push', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.create), - { ...getUserAction('comment', Actions.create), commentId: 'muahaha' }, - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [ - userActions[userActions.length - 2].commentId, - userActions[userActions.length - 1].commentId, - ], - hasDataToPush: true, - }, - }, - }); - }); - - it('Correctly marks first/last index and multiple comment ids, one needs push', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.create), - getUserAction('pushed', Actions.push_to_service), - { ...getUserAction('comment', Actions.create), commentId: 'muahaha' }, - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 5, - commentsToUpdate: [userActions[userActions.length - 1].commentId], - hasDataToPush: true, - }, - }, - }); - }); - - it('Correctly marks first/last index and multiple comment ids, one needs push and one needs update', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.create), - getUserAction('pushed', Actions.push_to_service), - { ...getUserAction('comment', Actions.create), commentId: 'muahaha' }, - getUserAction('comment', Actions.update), - getUserAction('comment', Actions.update), - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 5, - commentsToUpdate: [ - userActions[userActions.length - 3].commentId, - userActions[userActions.length - 1].commentId, - ], - hasDataToPush: true, - }, - }, - }); - }); - - it('Does not count connector update as a reason to push', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('connector', Actions.update), - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Correctly handles multiple push actions', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.create), - getUserAction('pushed', Actions.push_to_service), - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 5, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Correctly handles comment update with multiple push actions', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.create), - getUserAction('pushed', Actions.push_to_service), - getUserAction('comment', Actions.update), - ]; - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 5, - commentsToUpdate: [userActions[userActions.length - 1].commentId], - hasDataToPush: true, - }, - }, - }); - }); - - it('Multiple connector tracking - hasDataToPush: true', () => { - const pushAction123 = getUserAction('pushed', Actions.push_to_service); - const push456 = { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - }; - - const pushAction456 = getUserAction('pushed', Actions.push_to_service, { - payload: { externalService: push456 }, - }); - - const userActions = [ - ...caseUserActions, - pushAction123, - getUserAction('comment', Actions.create), - pushAction456, - ]; - - const result = getPushedInfo(userActions, '123'); - - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [userActions[userActions.length - 2].commentId], - hasDataToPush: true, - }, - '456': { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - firstPushIndex: 5, - lastPushIndex: 5, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Multiple connector tracking - hasDataToPush: false', () => { - const pushAction123 = getUserAction('pushed', Actions.push_to_service); - const push456 = { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - }; - - const pushAction456 = getUserAction('pushed', Actions.push_to_service, { - payload: { externalService: push456 }, - }); - - const userActions = [ - ...caseUserActions, - pushAction123, - getUserAction('comment', Actions.create), - pushAction456, - ]; - - const result = getPushedInfo(userActions, '456'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [userActions[userActions.length - 2].commentId], - hasDataToPush: true, - }, - '456': { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - firstPushIndex: 5, - lastPushIndex: 5, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Change fields of current connector - hasDataToPush: true', () => { - const userActions = [ - ...caseUserActions, - createUpdate123HighPriorityConnector(), - getUserAction('pushed', Actions.push_to_service), - createUpdate123LowPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 4, - lastPushIndex: 4, - commentsToUpdate: [], - hasDataToPush: true, - }, - }, - }); - }); - - it('Change current connector - hasDataToPush: true', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - createUpdate456HighPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Change connector and back - hasDataToPush: true', () => { - const userActions = [ - ...caseUserActions, - getUserAction('pushed', Actions.push_to_service), - createUpdate456HighPriorityConnector(), - createUpdate123HighPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 3, - lastPushIndex: 3, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Change fields and connector after push - hasDataToPush: true', () => { - const userActions = [ - ...caseUserActions, - createUpdate123HighPriorityConnector(), - getUserAction('pushed', Actions.push_to_service), - createUpdate456HighPriorityConnector(), - createUpdate123LowPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 4, - lastPushIndex: 4, - commentsToUpdate: [], - hasDataToPush: true, - }, - }, - }); - }); - - it('Change only connector after push - hasDataToPush: false', () => { - const userActions = [ - ...caseUserActions, - createUpdate123HighPriorityConnector(), - getUserAction('pushed', Actions.push_to_service), - createUpdate456HighPriorityConnector(), - createUpdate123HighPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 4, - lastPushIndex: 4, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Change connectors and fields - multiple pushes', () => { - const pushAction123 = getUserAction('pushed', Actions.push_to_service); - const push456 = { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - }; - - const pushAction456 = getUserAction('pushed', Actions.push_to_service, { - payload: { externalService: push456 }, - }); - - const userActions = [ - ...caseUserActions, - createUpdate123HighPriorityConnector(), - pushAction123, - createUpdate456HighPriorityConnector(), - pushAction456, - createUpdate123LowPriorityConnector(), - createUpdate456HighPriorityConnector(), - createUpdate123LowPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: true, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 4, - lastPushIndex: 4, - commentsToUpdate: [], - hasDataToPush: true, - }, - '456': { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - firstPushIndex: 6, - lastPushIndex: 6, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('pushing other connectors does not count as an update', () => { - const pushAction123 = getUserAction('pushed', Actions.push_to_service); - const push456 = { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - }; - - const pushAction456 = getUserAction('pushed', Actions.push_to_service, { - payload: { externalService: push456 }, - }); - - const userActions = [ - ...caseUserActions, - createUpdate123HighPriorityConnector(), - pushAction123, - createUpdate456HighPriorityConnector(), - pushAction456, - createUpdate123HighPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 4, - lastPushIndex: 4, - commentsToUpdate: [], - hasDataToPush: false, - }, - '456': { - ...basicPush, - connectorId: '456', - connectorName: 'other connector name', - externalId: 'other_external_id', - firstPushIndex: 6, - lastPushIndex: 6, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); - }); - - it('Changing other connectors fields does not count as an update', () => { - const userActions = [ - ...caseUserActions, - createUpdate123HighPriorityConnector(), - getUserAction('pushed', Actions.push_to_service), - createUpdate456HighPriorityConnector(), - createUpdate456HighPriorityConnector(), - ]; - - const result = getPushedInfo(userActions, '123'); - expect(result).toEqual({ - hasDataToPush: false, - caseServices: { - '123': { - ...basicPush, - firstPushIndex: 4, - lastPushIndex: 4, - commentsToUpdate: [], - hasDataToPush: false, - }, - }, - }); }); }); }); - -const jira123HighPriorityFields = { - fields: { ...jiraFields.fields, priority: 'High' }, -}; - -const jira123LowPriorityFields = { - fields: { ...jiraFields.fields, priority: 'Low' }, -}; - -const jira456Fields = { - fields: { issueType: '10', parent: null, priority: null }, -}; - -const jira456HighPriorityFields = { - id: '456', - fields: { ...jira456Fields.fields, priority: 'High' }, -}; - -const createUpdate123HighPriorityConnector = () => - getUserAction('connector', Actions.update, { - payload: { connector: getJiraConnector(jira123HighPriorityFields) }, - }); - -const createUpdate123LowPriorityConnector = () => - getUserAction('connector', Actions.update, { - payload: { connector: getJiraConnector(jira123LowPriorityFields) }, - }); - -const createUpdate456HighPriorityConnector = () => - getUserAction('connector', Actions.update, { - payload: { connector: getJiraConnector(jira456HighPriorityFields) }, - }); diff --git a/x-pack/plugins/cases/public/containers/use_find_case_user_actions.tsx b/x-pack/plugins/cases/public/containers/use_find_case_user_actions.tsx index 46236ed36be3d..8470ea439d6fa 100644 --- a/x-pack/plugins/cases/public/containers/use_find_case_user_actions.tsx +++ b/x-pack/plugins/cases/public/containers/use_find_case_user_actions.tsx @@ -6,209 +6,17 @@ */ import { isEmpty, uniqBy } from 'lodash/fp'; -import deepEqual from 'fast-deep-equal'; import { useQuery } from '@tanstack/react-query'; -import type { CaseUserActions, CaseExternalService } from '../../common/ui/types'; -import type { CaseConnector } from '../../common/api'; -import { ActionTypes, NONE_CONNECTOR_ID } from '../../common/api'; +import type { CaseUserActions } from '../../common/ui/types'; +import { ActionTypes } from '../../common/api'; import { findCaseUserActions } from './api'; -import { - isPushedUserAction, - isConnectorUserAction, - isCreateCaseUserAction, -} from '../../common/utils/user_actions'; +import { isPushedUserAction } from '../../common/utils/user_actions'; import type { ServerError } from '../types'; import { useToasts } from '../common/lib/kibana'; import { ERROR_TITLE } from './translations'; import { casesQueriesKeys } from './constants'; -export interface CaseService extends CaseExternalService { - firstPushIndex: number; - lastPushIndex: number; - commentsToUpdate: string[]; - hasDataToPush: boolean; -} - -export interface CaseServices { - [key: string]: CaseService; -} - -const groupConnectorFields = ( - userActions: CaseUserActions[] -): Record> => - userActions.reduce((acc, mua) => { - if ( - (isConnectorUserAction(mua) || isCreateCaseUserAction(mua)) && - mua.payload?.connector?.id !== NONE_CONNECTOR_ID - ) { - const connector = mua.payload.connector; - - return { - ...acc, - [connector.id]: [...(acc[connector.id] || []), connector.fields], - }; - } - - return acc; - }, {} as Record>); - -const connectorHasChangedFields = ({ - connectorFieldsBeforePush, - connectorFieldsAfterPush, - connectorId, -}: { - connectorFieldsBeforePush: Record> | null; - connectorFieldsAfterPush: Record> | null; - connectorId: string; -}): boolean => { - if (connectorFieldsAfterPush == null || connectorFieldsAfterPush[connectorId] == null) { - return false; - } - - const fieldsAfterPush = connectorFieldsAfterPush[connectorId]; - - if (connectorFieldsBeforePush != null && connectorFieldsBeforePush[connectorId] != null) { - const fieldsBeforePush = connectorFieldsBeforePush[connectorId]; - return !deepEqual( - fieldsBeforePush[fieldsBeforePush.length - 1], - fieldsAfterPush[fieldsAfterPush.length - 1] - ); - } - - if (fieldsAfterPush.length >= 2) { - return !deepEqual( - fieldsAfterPush[fieldsAfterPush.length - 2], - fieldsAfterPush[fieldsAfterPush.length - 1] - ); - } - - return false; -}; - -interface CommentsAndIndex { - commentId: string; - commentIndex: number; -} - -export const getPushedInfo = ( - caseUserActions: CaseUserActions[], - caseConnectorId: string -): { - caseServices: CaseServices; - hasDataToPush: boolean; -} => { - const hasDataToPushForConnector = (connectorId: string): boolean => { - const caseUserActionsReversed = [...caseUserActions].reverse(); - const lastPushOfConnectorReversedIndex = caseUserActionsReversed.findIndex( - (mua) => - isPushedUserAction<'camelCase'>(mua) && - mua.payload.externalService.connectorId === connectorId - ); - - if (lastPushOfConnectorReversedIndex === -1) { - return true; - } - - const lastPushOfConnectorIndex = - caseUserActionsReversed.length - lastPushOfConnectorReversedIndex - 1; - - const actionsBeforePush = caseUserActions.slice(0, lastPushOfConnectorIndex); - const actionsAfterPush = caseUserActions.slice( - lastPushOfConnectorIndex + 1, - caseUserActionsReversed.length - ); - - const connectorFieldsBeforePush = groupConnectorFields(actionsBeforePush); - const connectorFieldsAfterPush = groupConnectorFields(actionsAfterPush); - - const connectorHasChanged = connectorHasChangedFields({ - connectorFieldsBeforePush, - connectorFieldsAfterPush, - connectorId, - }); - - return ( - actionsAfterPush.some( - (mua) => mua.type !== ActionTypes.connector && mua.type !== ActionTypes.pushed - ) || connectorHasChanged - ); - }; - - const commentsAndIndex = caseUserActions.reduce( - (bacc, mua, index) => - mua.type === ActionTypes.comment && mua.commentId != null - ? [ - ...bacc, - { - commentId: mua.commentId, - commentIndex: index, - }, - ] - : bacc, - [] - ); - - let caseServices = caseUserActions.reduce((acc, cua, i) => { - if (!isPushedUserAction<'camelCase'>(cua)) { - return acc; - } - - const externalService = cua.payload.externalService; - if (externalService === null) { - return acc; - } - - return { - ...acc, - ...(acc[externalService.connectorId] != null - ? { - [externalService.connectorId]: { - ...acc[externalService.connectorId], - ...externalService, - lastPushIndex: i, - commentsToUpdate: [], - }, - } - : { - [externalService.connectorId]: { - ...externalService, - firstPushIndex: i, - lastPushIndex: i, - hasDataToPush: hasDataToPushForConnector(externalService.connectorId), - commentsToUpdate: [], - }, - }), - }; - }, {}); - - caseServices = Object.keys(caseServices).reduce((acc, key) => { - return { - ...acc, - [key]: { - ...caseServices[key], - // if the comment happens after the lastUpdateToCaseIndex, it should be included in commentsToUpdate - commentsToUpdate: commentsAndIndex.reduce( - (bacc, currentComment) => - currentComment.commentIndex > caseServices[key].lastPushIndex - ? bacc.indexOf(currentComment.commentId) > -1 - ? [...bacc.filter((e) => e !== currentComment.commentId), currentComment.commentId] - : [...bacc, currentComment.commentId] - : bacc, - [] - ), - }, - }; - }, {}); - - const hasDataToPush = - caseServices[caseConnectorId] != null ? caseServices[caseConnectorId].hasDataToPush : true; - return { - hasDataToPush, - caseServices, - }; -}; - export const getProfileUids = (userActions: CaseUserActions[]) => { const uids = userActions.reduce>((acc, userAction) => { if (userAction.type === ActionTypes.assignees) { @@ -235,29 +43,25 @@ export const getProfileUids = (userActions: CaseUserActions[]) => { return uids; }; -export const useFindCaseUserActions = (caseId: string, caseConnectorId: string) => { +export const useFindCaseUserActions = (caseId: string) => { const toasts = useToasts(); const abortCtrlRef = new AbortController(); return useQuery( - casesQueriesKeys.userActions(caseId, caseConnectorId), + casesQueriesKeys.userActions(caseId), async () => { const response = await findCaseUserActions(caseId, abortCtrlRef.signal); const participants = !isEmpty(response.userActions) ? uniqBy('createdBy.username', response.userActions).map((cau) => cau.createdBy) : []; - const caseUserActions: CaseUserActions[] = !isEmpty(response.userActions) - ? response.userActions - : []; - const pushedInfo = getPushedInfo(caseUserActions, caseConnectorId); + const caseUserActions = !isEmpty(response.userActions) ? response.userActions : []; const profileUids = getProfileUids(caseUserActions); return { caseUserActions, participants, profileUids, - ...pushedInfo, }; }, { diff --git a/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx b/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx new file mode 100644 index 0000000000000..220d37deefb5b --- /dev/null +++ b/x-pack/plugins/cases/public/containers/use_get_case_connectors.test.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import * as api from './api'; +import type { AppMockRenderer } from '../common/mock'; +import { createAppMockRenderer } from '../common/mock'; +import { useToasts } from '../common/lib/kibana'; +import { useGetCaseConnectors } from './use_get_case_connectors'; + +jest.mock('./api'); +jest.mock('../common/lib/kibana'); + +describe('useGetCaseConnectors', () => { + const caseId = 'test-id'; + const abortCtrl = new AbortController(); + const addSuccess = jest.fn(); + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError: jest.fn() }); + + let appMockRender: AppMockRenderer; + + beforeEach(() => { + appMockRender = createAppMockRenderer(); + jest.clearAllMocks(); + }); + + it('calls getCaseConnectors with correct arguments', async () => { + const spyOnGetCases = jest.spyOn(api, 'getCaseConnectors'); + const { waitForNextUpdate } = renderHook(() => useGetCaseConnectors(caseId), { + wrapper: appMockRender.AppWrapper, + }); + + await waitForNextUpdate(); + + expect(spyOnGetCases).toBeCalledWith('test-id', abortCtrl.signal); + }); + + it('shows a toast error message when an error occurs in the response', async () => { + const spyOnGetCases = jest.spyOn(api, 'getCaseConnectors'); + spyOnGetCases.mockImplementation(() => { + throw new Error('Something went wrong'); + }); + + const addError = jest.fn(); + (useToasts as jest.Mock).mockReturnValue({ addSuccess, addError }); + + const { waitForNextUpdate } = renderHook(() => useGetCaseConnectors(caseId), { + wrapper: appMockRender.AppWrapper, + }); + + await waitForNextUpdate(); + expect(addError).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cases/public/containers/use_get_case_connectors.tsx b/x-pack/plugins/cases/public/containers/use_get_case_connectors.tsx new file mode 100644 index 0000000000000..fa7920a8c3553 --- /dev/null +++ b/x-pack/plugins/cases/public/containers/use_get_case_connectors.tsx @@ -0,0 +1,37 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import * as i18n from './translations'; +import { getCaseConnectors } from './api'; +import type { ServerError } from '../types'; +import { casesQueriesKeys } from './constants'; +import { useCasesToast } from '../common/use_cases_toast'; +import type { CaseConnectors } from './types'; + +// 30 seconds +const STALE_TIME = 1000 * 30; + +export const useGetCaseConnectors = (caseId: string) => { + const { showErrorToast } = useCasesToast(); + + return useQuery( + casesQueriesKeys.caseConnectors(caseId), + () => { + const abortCtrlRef = new AbortController(); + return getCaseConnectors(caseId, abortCtrlRef.signal); + }, + { + staleTime: STALE_TIME, + onError: (error: ServerError) => { + showErrorToast(error, { title: i18n.ERROR_TITLE }); + }, + } + ); +}; + +export type UseGetCaseConnectors = ReturnType; diff --git a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap index c373435795b17..193451e9d146a 100644 --- a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap +++ b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap @@ -84,6 +84,90 @@ Object { } `; +exports[`audit_logger log function event structure creates the correct audit event for operation: "bulkGetAttachments" with an error and entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_comment_bulk_get", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases-comments", + }, + }, + "message": "Failed attempt to access cases-comments [id=1] as owner \\"awesome\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "bulkGetAttachments" with an error but no entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_comment_bulk_get", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "message": "Failed attempt to access a comments as any owners", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "bulkGetAttachments" without an error but with an entity 1`] = ` +Object { + "event": Object { + "action": "case_comment_bulk_get", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "5", + "type": "cases-comments", + }, + }, + "message": "User has accessed cases-comments [id=5] as owner \\"super\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "bulkGetAttachments" without an error or entity 1`] = ` +Object { + "event": Object { + "action": "case_comment_bulk_get", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "message": "User has accessed a comments as any owners", +} +`; + exports[`audit_logger log function event structure creates the correct audit event for operation: "bulkGetCases" with an error and entity 1`] = ` Object { "error": Object { @@ -2100,6 +2184,90 @@ Object { } `; +exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" with an error and entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_user_action_get_stats", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases-user-actions", + }, + }, + "message": "Failed attempt to access cases-user-actions [id=1] as owner \\"awesome\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" with an error but no entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_user_action_get_stats", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "message": "Failed attempt to access a user actions as any owners", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" without an error but with an entity 1`] = ` +Object { + "event": Object { + "action": "case_user_action_get_stats", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "5", + "type": "cases-user-actions", + }, + }, + "message": "User has accessed cases-user-actions [id=5] as owner \\"super\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActionStats" without an error or entity 1`] = ` +Object { + "event": Object { + "action": "case_user_action_get_stats", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "message": "User has accessed a user actions as any owners", +} +`; + exports[`audit_logger log function event structure creates the correct audit event for operation: "getUserActions" with an error and entity 1`] = ` Object { "error": Object { diff --git a/x-pack/plugins/cases/server/authorization/authorization.test.ts b/x-pack/plugins/cases/server/authorization/authorization.test.ts index 612f521128685..0945555689fbf 100644 --- a/x-pack/plugins/cases/server/authorization/authorization.test.ts +++ b/x-pack/plugins/cases/server/authorization/authorization.test.ts @@ -91,6 +91,7 @@ describe('authorization', () => { describe('ensureAuthorized', () => { const feature = { id: '1', cases: ['a'] }; + const checkRequestReturningHasAllAsTrue = jest.fn(async () => ({ hasAllRequested: true })); let securityStart: ReturnType; let featuresStart: jest.Mocked; @@ -101,7 +102,7 @@ describe('authorization', () => { securityStart = securityMock.createStart(); securityStart.authz.mode.useRbacForRequest.mockReturnValue(true); securityStart.authz.checkPrivilegesDynamicallyWithRequest.mockReturnValue( - jest.fn(async () => ({ hasAllRequested: true })) + checkRequestReturningHasAllAsTrue ); featuresStart = featuresPluginMock.createStart(); @@ -119,6 +120,34 @@ describe('authorization', () => { }); }); + it('calls checkRequest with no repeated owners', async () => { + expect.assertions(2); + + const casesGet = securityStart.authz.actions.cases.get as jest.Mock; + casesGet.mockImplementation((owner, op) => `${owner}/${op}`); + + try { + await auth.ensureAuthorized({ + entities: [ + { id: '1', owner: 'b' }, + { id: '2', owner: 'b' }, + ], + operation: Operations.createCase, + }); + } catch (error) { + expect(checkRequestReturningHasAllAsTrue).toBeCalledTimes(1); + expect(checkRequestReturningHasAllAsTrue.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "kibana": Array [ + "b/createCase", + ], + }, + ] + `); + } + }); + it('throws an error when the owner passed in is not included in the features when security is disabled', async () => { expect.assertions(1); securityStart.authz.mode.useRbacForRequest.mockReturnValue(false); @@ -133,6 +162,23 @@ describe('authorization', () => { } }); + it('throws an error with a single owner when the repeated owners passed in are not included in the features when security is disabled', async () => { + expect.assertions(1); + securityStart.authz.mode.useRbacForRequest.mockReturnValue(false); + + try { + await auth.ensureAuthorized({ + entities: [ + { id: '1', owner: 'b' }, + { id: '2', owner: 'b' }, + ], + operation: Operations.createCase, + }); + } catch (error) { + expect(error.message).toBe('Unauthorized to create case with owners: "b"'); + } + }); + it('throws an error when the owner passed in is not included in the features when security undefined', async () => { expect.assertions(1); @@ -154,6 +200,30 @@ describe('authorization', () => { } }); + it('throws an error with a single owner when the repeated owners passed in are not included in the features when security undefined', async () => { + expect.assertions(1); + + auth = await Authorization.create({ + request, + spaces: spacesStart, + features: featuresStart, + auditLogger: new AuthorizationAuditLogger(mockLogger), + logger: loggingSystemMock.createLogger(), + }); + + try { + await auth.ensureAuthorized({ + entities: [ + { id: '1', owner: 'b' }, + { id: '1', owner: 'b' }, + ], + operation: Operations.createCase, + }); + } catch (error) { + expect(error.message).toBe('Unauthorized to create case with owners: "b"'); + } + }); + it('throws an error when the owner passed in is not included in the features when security is enabled', async () => { expect.assertions(1); @@ -167,6 +237,22 @@ describe('authorization', () => { } }); + it('throws an error with a single owner when the repeated owners passed in are not included in the features when security is enabled', async () => { + expect.assertions(1); + + try { + await auth.ensureAuthorized({ + entities: [ + { id: '1', owner: 'b' }, + { id: '2', owner: 'b' }, + ], + operation: Operations.createCase, + }); + } catch (error) { + expect(error.message).toBe('Unauthorized to create case with owners: "b"'); + } + }); + it('logs the error thrown when the passed in owner is not one of the features', async () => { expect.assertions(2); @@ -254,6 +340,26 @@ describe('authorization', () => { } }); + it('throws an error with a single owner listed when the user does not have all the requested privileges', async () => { + expect.assertions(1); + + securityStart.authz.checkPrivilegesDynamicallyWithRequest.mockReturnValue( + jest.fn(async () => ({ hasAllRequested: false })) + ); + + try { + await auth.ensureAuthorized({ + entities: [ + { id: '1', owner: 'a' }, + { id: '2', owner: 'a' }, + ], + operation: Operations.createCase, + }); + } catch (error) { + expect(error.message).toBe('Unauthorized to create case with owners: "a"'); + } + }); + it('throws an error when owner does not exist because it was from a disabled plugin', async () => { expect.assertions(1); @@ -1038,6 +1144,7 @@ describe('authorization', () => { { id: '1', attributes: { owner: 'b' }, type: 'test', references: [] }, { id: '2', attributes: { owner: 'c' }, type: 'test', references: [] }, ], + operation: Operations.bulkGetCases, }); } catch (error) { expect(error.message).toBe('Unauthorized to access cases of any owner'); @@ -1115,6 +1222,7 @@ describe('authorization', () => { { id: '1', attributes: { owner: 'a' }, type: 'test', references: [] }, { id: '2', attributes: { owner: 'b' }, type: 'test', references: [] }, ], + operation: Operations.bulkGetCases, }); await expect(helpersPromise).resolves.not.toThrow(); @@ -1183,6 +1291,7 @@ describe('authorization', () => { { id: '2', attributes: { owner: 'b' }, type: 'test', references: [] }, { id: '3', attributes: { owner: 'c' }, type: 'test', references: [] }, ], + operation: Operations.bulkGetCases, }); expect(res).toEqual({ @@ -1305,6 +1414,7 @@ describe('authorization', () => { { id: '2', attributes: { owner: 'b' }, type: 'test', references: [] }, { id: '3', attributes: { owner: 'c' }, type: 'test', references: [] }, ], + operation: Operations.bulkGetCases, }); expect(res).toEqual({ diff --git a/x-pack/plugins/cases/server/authorization/authorization.ts b/x-pack/plugins/cases/server/authorization/authorization.ts index 884ae817a7a95..14ecaa6111f8c 100644 --- a/x-pack/plugins/cases/server/authorization/authorization.ts +++ b/x-pack/plugins/cases/server/authorization/authorization.ts @@ -14,7 +14,7 @@ import type { Space, SpacesPluginStart } from '@kbn/spaces-plugin/server'; import type { AuthFilterHelpers, OwnerEntity } from './types'; import { getOwnersFilter, groupByAuthorization } from './utils'; import type { OperationDetails } from '.'; -import { AuthorizationAuditLogger, Operations } from '.'; +import { AuthorizationAuditLogger } from '.'; import { createCaseError } from '../common/error'; /** @@ -109,10 +109,9 @@ export class Authorization { operation: OperationDetails; }) { try { - await this._ensureAuthorized( - entities.map((entity) => entity.owner), - operation - ); + const uniqueOwners = Array.from(new Set(entities.map((entity) => entity.owner))); + + await this._ensureAuthorized(uniqueOwners, operation); } catch (error) { this.logSavedObjects({ entities, operation, error }); throw error; @@ -128,13 +127,15 @@ export class Authorization { * * @param savedObjects an array of saved objects to be authorized. Each saved objects should contain * an ID and an owner + * @param operation the operation that should be authorized */ public async getAndEnsureAuthorizedEntities({ savedObjects, + operation, }: { savedObjects: Array>; + operation: OperationDetails; }): Promise<{ authorized: Array>; unauthorized: Array> }> { - const operation = Operations.bulkGetCases; const entities = savedObjects.map((so) => ({ id: so.id, owner: so.attributes.owner, diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index 9a4c3cdcdf9aa..cbfef6f4713e3 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -230,6 +230,14 @@ const AttachmentOperations = { docType: 'comments', savedObjectType: CASE_COMMENT_SAVED_OBJECT, }, + [ReadOperations.BulkGetAttachments]: { + ecsType: EVENT_TYPES.access, + name: ACCESS_COMMENT_OPERATION, + action: 'case_comment_bulk_get', + verbs: accessVerbs, + docType: 'comments', + savedObjectType: CASE_COMMENT_SAVED_OBJECT, + }, [ReadOperations.GetAllComments]: { ecsType: EVENT_TYPES.access, name: ACCESS_COMMENT_OPERATION, @@ -351,4 +359,12 @@ export const Operations: Record; + +type AttachmentSavedObject = SavedObject; + +/** + * Retrieves multiple attachments by id. + */ +export async function bulkGet( + { attachmentIDs, caseID }: BulkGetArgs, + clientArgs: CasesClientArgs, + casesClient: CasesClient +): Promise { + const { + services: { attachmentService }, + logger, + authorization, + } = clientArgs; + + try { + const request = pipe( + excess(BulkGetAttachmentsRequestRt).decode({ ids: attachmentIDs }), + fold(throwErrors(Boom.badRequest), identity) + ); + + throwErrorIfIdsExceedTheLimit(request.ids); + + // perform an authorization check for the case + await casesClient.cases.resolve({ id: caseID }); + + const attachments = await attachmentService.getter.bulkGet(request.ids); + + const { validAttachments, attachmentsWithErrors, invalidAssociationAttachments } = + partitionAttachments(caseID, attachments); + + const { authorized: authorizedAttachments, unauthorized: unauthorizedAttachments } = + await authorization.getAndEnsureAuthorizedEntities({ + savedObjects: validAttachments, + operation: Operations.bulkGetAttachments, + }); + + const errors = constructErrors({ + associationErrors: invalidAssociationAttachments, + unauthorizedAttachments, + soBulkGetErrors: attachmentsWithErrors, + caseId: caseID, + }); + + return BulkGetAttachmentsResponseRt.encode({ + attachments: flattenCommentSavedObjects(authorizedAttachments), + errors, + }); + } catch (error) { + throw createCaseError({ + message: `Failed to bulk get attachments for case id: ${caseID}: ${error}`, + error, + logger, + }); + } +} + +const throwErrorIfIdsExceedTheLimit = (ids: string[]) => { + if (ids.length > MAX_BULK_GET_ATTACHMENTS) { + throw Boom.badRequest( + `Maximum request limit of ${MAX_BULK_GET_ATTACHMENTS} attachments reached` + ); + } +}; + +interface PartitionedAttachments { + validAttachments: AttachmentSavedObject[]; + attachmentsWithErrors: AttachmentSavedObjectWithErrors; + invalidAssociationAttachments: AttachmentSavedObject[]; +} + +const partitionAttachments = ( + caseId: string, + attachments: BulkOptionalAttributes +): PartitionedAttachments => { + const [attachmentsWithoutErrors, errors] = partitionBySOError(attachments.saved_objects); + const [caseAttachments, invalidAssociationAttachments] = partitionByCaseAssociation( + caseId, + attachmentsWithoutErrors + ); + + return { + validAttachments: caseAttachments, + attachmentsWithErrors: errors, + invalidAssociationAttachments, + }; +}; + +const partitionBySOError = (attachments: Array>) => + partition( + attachments, + (attachment) => attachment.error == null && attachment.attributes != null + ) as [AttachmentSavedObject[], AttachmentSavedObjectWithErrors]; + +const partitionByCaseAssociation = (caseId: string, attachments: AttachmentSavedObject[]) => + partition(attachments, (attachment) => { + const ref = getCaseReference(attachment.references); + + return caseId === ref?.id; + }); + +const getCaseReference = (references: SavedObjectReference[]): SavedObjectReference | undefined => { + return references.find((ref) => ref.name === CASE_REF_NAME && ref.type === CASE_SAVED_OBJECT); +}; + +const constructErrors = ({ + caseId, + soBulkGetErrors, + associationErrors, + unauthorizedAttachments, +}: { + caseId: string; + soBulkGetErrors: AttachmentSavedObjectWithErrors; + associationErrors: AttachmentSavedObject[]; + unauthorizedAttachments: AttachmentSavedObject[]; +}): BulkGetAttachmentsResponse['errors'] => { + const errors: BulkGetAttachmentsResponse['errors'] = []; + + for (const soError of soBulkGetErrors) { + errors.push({ + error: soError.error.error, + message: soError.error.message, + status: soError.error.statusCode, + attachmentId: soError.id, + }); + } + + for (const attachment of associationErrors) { + errors.push({ + error: 'Bad Request', + message: `Attachment is not attached to case id=${caseId}`, + status: 400, + attachmentId: attachment.id, + }); + } + + for (const unauthorizedAttachment of unauthorizedAttachments) { + errors.push({ + error: 'Forbidden', + message: `Unauthorized to access attachment with owner: "${unauthorizedAttachment.attributes.owner}"`, + status: 403, + attachmentId: unauthorizedAttachment.id, + }); + } + + return errors; +}; diff --git a/x-pack/plugins/cases/server/client/attachments/client.ts b/x-pack/plugins/cases/server/client/attachments/client.ts index c8cb26a886acf..5647427f76cf4 100644 --- a/x-pack/plugins/cases/server/client/attachments/client.ts +++ b/x-pack/plugins/cases/server/client/attachments/client.ts @@ -5,21 +5,35 @@ * 2.0. */ -import type { AlertResponse, CommentResponse } from '../../../common/api'; +import type { + AlertResponse, + AllCommentsResponse, + BulkGetAttachmentsResponse, + CaseResponse, + CommentResponse, + CommentsResponse, +} from '../../../common/api'; import type { CasesClient } from '../client'; import type { CasesClientInternal } from '../client_internal'; -import type { IAllCommentsResponse, ICaseResponse, ICommentsResponse } from '../typedoc_interfaces'; import type { CasesClientArgs } from '../types'; -import type { AddArgs } from './add'; import { addComment } from './add'; -import type { BulkCreateArgs } from './bulk_create'; +import type { + BulkCreateArgs, + AddArgs, + DeleteAllArgs, + DeleteArgs, + FindArgs, + GetAllAlertsAttachToCase, + GetAllArgs, + GetArgs, + UpdateArgs, + BulkGetArgs, +} from './types'; import { bulkCreate } from './bulk_create'; -import type { DeleteAllArgs, DeleteArgs } from './delete'; import { deleteAll, deleteComment } from './delete'; -import type { FindArgs, GetAllAlertsAttachToCase, GetAllArgs, GetArgs } from './get'; import { find, get, getAll, getAllAlertsAttachToCase } from './get'; -import type { UpdateArgs } from './update'; +import { bulkGet } from './bulk_get'; import { update } from './update'; /** @@ -29,8 +43,9 @@ export interface AttachmentsSubClient { /** * Adds an attachment to a case. */ - add(params: AddArgs): Promise; - bulkCreate(params: BulkCreateArgs): Promise; + add(params: AddArgs): Promise; + bulkCreate(params: BulkCreateArgs): Promise; + bulkGet(params: BulkGetArgs): Promise; /** * Deletes all attachments associated with a single case. */ @@ -42,7 +57,7 @@ export interface AttachmentsSubClient { /** * Retrieves all comments matching the search criteria. */ - find(findArgs: FindArgs): Promise; + find(findArgs: FindArgs): Promise; /** * Retrieves all alerts attach to a case given a single case ID */ @@ -50,7 +65,7 @@ export interface AttachmentsSubClient { /** * Gets all attachments for a single case. */ - getAll(getAllArgs: GetAllArgs): Promise; + getAll(getAllArgs: GetAllArgs): Promise; /** * Retrieves a single attachment for a case. */ @@ -60,7 +75,7 @@ export interface AttachmentsSubClient { * * The request must include all fields for the attachment. Even the fields that are not changing. */ - update(updateArgs: UpdateArgs): Promise; + update(updateArgs: UpdateArgs): Promise; } /** @@ -76,6 +91,7 @@ export const createAttachmentsSubClient = ( const attachmentSubClient: AttachmentsSubClient = { add: (params: AddArgs) => addComment(params, clientArgs), bulkCreate: (params: BulkCreateArgs) => bulkCreate(params, clientArgs), + bulkGet: (params) => bulkGet(params, clientArgs, casesClient), deleteAll: (deleteAllArgs: DeleteAllArgs) => deleteAll(deleteAllArgs, clientArgs), delete: (deleteArgs: DeleteArgs) => deleteComment(deleteArgs, clientArgs), find: (findArgs: FindArgs) => find(findArgs, clientArgs), diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index 22d739a3d9379..c1a8e019ff0f8 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -15,30 +15,7 @@ import { CASE_SAVED_OBJECT, MAX_CONCURRENT_SEARCHES } from '../../../common/cons import type { CasesClientArgs } from '../types'; import { createCaseError } from '../../common/error'; import { Operations } from '../../authorization'; - -/** - * Parameters for deleting all comments of a case. - */ -export interface DeleteAllArgs { - /** - * The case ID to delete all attachments for - */ - caseID: string; -} - -/** - * Parameters for deleting a single attachment of a case. - */ -export interface DeleteArgs { - /** - * The case ID to delete an attachment from - */ - caseID: string; - /** - * The attachment ID to delete - */ - attachmentID: string; -} +import type { DeleteAllArgs, DeleteArgs } from './types'; /** * Delete all comments for a case. @@ -51,7 +28,6 @@ export async function deleteAll( ): Promise { const { user, - unsecuredSavedObjectsClient, services: { caseService, attachmentService, userActionService }, logger, authorization, @@ -76,7 +52,6 @@ export async function deleteAll( const mapper = async (comment: SavedObject) => attachmentService.delete({ - unsecuredSavedObjectsClient, attachmentId: comment.id, refresh: false, }); @@ -115,15 +90,13 @@ export async function deleteComment( ) { const { user, - unsecuredSavedObjectsClient, services: { attachmentService, userActionService }, logger, authorization, } = clientArgs; try { - const myComment = await attachmentService.get({ - unsecuredSavedObjectsClient, + const myComment = await attachmentService.getter.get({ attachmentId: attachmentID, }); @@ -145,7 +118,6 @@ export async function deleteComment( } await attachmentService.delete({ - unsecuredSavedObjectsClient, attachmentId: attachmentID, refresh: false, }); diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts index adbdd3abf2448..91160e857c5cf 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.ts +++ b/x-pack/plugins/cases/server/client/attachments/get.ts @@ -12,7 +12,6 @@ import type { AttributesTypeAlerts, CommentResponse, CommentsResponse, - FindQueryParams, } from '../../../common/api'; import { AllCommentsResponseRt, CommentResponseRt, CommentsResponseRt } from '../../../common/api'; import { @@ -29,48 +28,7 @@ import { combineFilters, stringToKueryNode } from '../utils'; import { Operations } from '../../authorization'; import { includeFieldsRequiredForAuthentication } from '../../authorization/utils'; import type { CasesClient } from '../client'; - -/** - * Parameters for finding attachments of a case - */ -export interface FindArgs { - /** - * The case ID for finding associated attachments - */ - caseID: string; - /** - * Optional parameters for filtering the returned attachments - */ - queryParams?: FindQueryParams; -} - -/** - * Parameters for retrieving all attachments of a case - */ -export interface GetAllArgs { - /** - * The case ID to retrieve all attachments for - */ - caseID: string; -} - -export interface GetArgs { - /** - * The ID of the case to retrieve an attachment from - */ - caseID: string; - /** - * The ID of the attachment to retrieve - */ - attachmentID: string; -} - -export interface GetAllAlertsAttachToCase { - /** - * The ID of the case to retrieve the alerts from - */ - caseId: string; -} +import type { FindArgs, GetAllAlertsAttachToCase, GetAllArgs, GetArgs } from './types'; const normalizeAlertResponse = (alerts: Array>): AlertResponse => alerts.reduce((acc: AlertResponse, alert) => { @@ -92,8 +50,6 @@ const normalizeAlertResponse = (alerts: Array> /** * Retrieves all alerts attached to a specific case. - * - * @ignore */ export const getAllAlertsAttachToCase = async ( { caseId }: GetAllAlertsAttachToCase, @@ -101,7 +57,6 @@ export const getAllAlertsAttachToCase = async ( casesClient: CasesClient ): Promise => { const { - unsecuredSavedObjectsClient, authorization, services: { attachmentService }, logger, @@ -117,8 +72,7 @@ export const getAllAlertsAttachToCase = async ( const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } = await authorization.getAuthorizationFilter(Operations.getAlertsAttachedToCase); - const alerts = await attachmentService.getAllAlertsAttachToCase({ - unsecuredSavedObjectsClient, + const alerts = await attachmentService.getter.getAllAlertsAttachToCase({ caseId: theCase.id, filter: authorizationFilter, }); @@ -142,8 +96,6 @@ export const getAllAlertsAttachToCase = async ( /** * Retrieves the attachments for a case entity. This support pagination. - * - * @ignore */ export async function find( { caseID, queryParams }: FindArgs, @@ -219,8 +171,6 @@ export async function find( /** * Retrieves a single attachment by its ID. - * - * @ignore */ export async function get( { attachmentID, caseID }: GetArgs, @@ -228,14 +178,12 @@ export async function get( ): Promise { const { services: { attachmentService }, - unsecuredSavedObjectsClient, logger, authorization, } = clientArgs; try { - const comment = await attachmentService.get({ - unsecuredSavedObjectsClient, + const comment = await attachmentService.getter.get({ attachmentId: attachmentID, }); @@ -256,8 +204,6 @@ export async function get( /** * Retrieves all the attachments for a case. - * - * @ignore */ export async function getAll( { caseID }: GetAllArgs, diff --git a/x-pack/plugins/cases/server/client/attachments/types.ts b/x-pack/plugins/cases/server/client/attachments/types.ts new file mode 100644 index 0000000000000..51c1de4fe3877 --- /dev/null +++ b/x-pack/plugins/cases/server/client/attachments/types.ts @@ -0,0 +1,120 @@ +/* + * 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 { + BulkCreateCommentRequest, + CommentPatchRequest, + CommentRequest, + FindQueryParams, +} from '../../../common/api'; + +/** + * The arguments needed for creating a new attachment to a case. + */ +export interface AddArgs { + /** + * The case ID that this attachment will be associated with + */ + caseId: string; + /** + * The attachment values. + */ + comment: CommentRequest; +} + +export interface BulkCreateArgs { + caseId: string; + attachments: BulkCreateCommentRequest; +} + +/** + * Parameters for deleting all comments of a case. + */ +export interface DeleteAllArgs { + /** + * The case ID to delete all attachments for + */ + caseID: string; +} + +/** + * Parameters for deleting a single attachment of a case. + */ +export interface DeleteArgs { + /** + * The case ID to delete an attachment from + */ + caseID: string; + /** + * The attachment ID to delete + */ + attachmentID: string; +} + +/** + * Parameters for finding attachments of a case + */ +export interface FindArgs { + /** + * The case ID for finding associated attachments + */ + caseID: string; + /** + * Optional parameters for filtering the returned attachments + */ + queryParams?: FindQueryParams; +} + +/** + * Parameters for retrieving all attachments of a case + */ +export interface GetAllArgs { + /** + * The case ID to retrieve all attachments for + */ + caseID: string; +} + +export interface GetArgs { + /** + * The ID of the case to retrieve an attachment from + */ + caseID: string; + /** + * The ID of the attachment to retrieve + */ + attachmentID: string; +} + +export interface BulkGetArgs { + caseID: string; + /** + * The ids of the attachments + */ + attachmentIDs: string[]; +} + +export interface GetAllAlertsAttachToCase { + /** + * The ID of the case to retrieve the alerts from + */ + caseId: string; +} + +/** + * Parameters for updating a single attachment + */ +export interface UpdateArgs { + /** + * The ID of the case that is associated with this attachment + */ + caseID: string; + /** + * The full attachment request with the fields updated with appropriate values + */ + updateRequest: CommentPatchRequest; +} diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts index f1ce5568c78bb..f2ed100243b08 100644 --- a/x-pack/plugins/cases/server/client/attachments/update.ts +++ b/x-pack/plugins/cases/server/client/attachments/update.ts @@ -10,25 +10,12 @@ import Boom from '@hapi/boom'; import { CaseCommentModel } from '../../common/models'; import { createCaseError } from '../../common/error'; import { isCommentRequestTypeExternalReference } from '../../../common/utils/attachments'; -import type { CaseResponse, CommentPatchRequest } from '../../../common/api'; +import type { CaseResponse } from '../../../common/api'; import { CASE_SAVED_OBJECT } from '../../../common/constants'; import type { CasesClientArgs } from '..'; import { decodeCommentRequest } from '../utils'; import { Operations } from '../../authorization'; - -/** - * Parameters for updating a single attachment - */ -export interface UpdateArgs { - /** - * The ID of the case that is associated with this attachment - */ - caseID: string; - /** - * The full attachment request with the fields updated with appropriate values - */ - updateRequest: CommentPatchRequest; -} +import type { UpdateArgs } from './types'; /** * Update an attachment. @@ -41,7 +28,6 @@ export async function update( ): Promise { const { services: { attachmentService }, - unsecuredSavedObjectsClient, logger, authorization, } = clientArgs; @@ -55,8 +41,7 @@ export async function update( decodeCommentRequest(queryRestAttributes); - const myComment = await attachmentService.get({ - unsecuredSavedObjectsClient, + const myComment = await attachmentService.getter.get({ attachmentId: queryCommentId, }); diff --git a/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts b/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts index f08f79a653c4f..8628fa64056ad 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_get.test.ts @@ -32,7 +32,7 @@ describe('bulkGet', () => { const caseSO = mockCases[0]; const clientArgs = createCasesClientMockArgs(); clientArgs.services.caseService.getCases.mockResolvedValue({ saved_objects: [caseSO] }); - clientArgs.services.attachmentService.getCaseCommentStats.mockResolvedValue(new Map()); + clientArgs.services.attachmentService.getter.getCaseCommentStats.mockResolvedValue(new Map()); clientArgs.authorization.getAndEnsureAuthorizedEntities.mockResolvedValue({ authorized: [caseSO], diff --git a/x-pack/plugins/cases/server/client/cases/bulk_get.ts b/x-pack/plugins/cases/server/client/cases/bulk_get.ts index c5f1c4541c283..85952b84bef91 100644 --- a/x-pack/plugins/cases/server/client/cases/bulk_get.ts +++ b/x-pack/plugins/cases/server/client/cases/bulk_get.ts @@ -11,13 +11,13 @@ import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pick, partition } from 'lodash'; -import type { SavedObjectError } from '@kbn/core-saved-objects-common'; import { MAX_BULK_GET_CASES } from '../../../common/constants'; import type { CasesBulkGetResponse, CasesBulkGetResponseCertainFields, CasesBulkGetRequestCertainFields, CaseResponse, + CaseAttributes, } from '../../../common/api'; import { CasesBulkGetRequestRt, @@ -30,11 +30,12 @@ import { import { getTypeProps } from '../../../common/api/runtime_types'; import { createCaseError } from '../../common/error'; import { asArray, flattenCaseSavedObject } from '../../common/utils'; -import type { CasesClientArgs } from '../types'; +import type { CasesClientArgs, SOWithErrors } from '../types'; import { includeFieldsRequiredForAuthentication } from '../../authorization/utils'; import type { CaseSavedObject } from '../../common/types'; +import { Operations } from '../../authorization'; -type SOWithErrors = Array; +type CaseSavedObjectWithErrors = SOWithErrors; /** * Retrieves multiple cases by ids. @@ -47,7 +48,6 @@ export const bulkGet = async caseInfo.error === undefined - ) as [CaseSavedObject[], SOWithErrors]; + ) as [CaseSavedObject[], CaseSavedObjectWithErrors]; const { authorized: authorizedCases, unauthorized: unauthorizedCases } = - await authorization.getAndEnsureAuthorizedEntities({ savedObjects: validCases }); + await authorization.getAndEnsureAuthorizedEntities({ + savedObjects: validCases, + operation: Operations.bulkGetCases, + }); const requestForTotals = ['totalComment', 'totalAlerts'].some( (totalKey) => !fields || fields.includes(totalKey) ); const commentTotals = requestForTotals - ? await attachmentService.getCaseCommentStats({ - unsecuredSavedObjectsClient, + ? await attachmentService.getter.getCaseCommentStats({ caseIds: authorizedCases.map((theCase) => theCase.id), }) : new Map(); @@ -139,7 +141,7 @@ const throwErrorIfCaseIdsReachTheLimit = (ids: string[]) => { }; const constructErrors = ( - soBulkGetErrors: SOWithErrors, + soBulkGetErrors: CaseSavedObjectWithErrors, unauthorizedCases: CaseSavedObject[] ): CasesBulkGetResponse['errors'] => { const errors: CasesBulkGetResponse['errors'] = []; diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index 81f5df0d2d2b3..d9e0b6383af1b 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -24,7 +24,6 @@ import { Operations } from '../../authorization'; */ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): Promise { const { - unsecuredSavedObjectsClient, services: { caseService, attachmentService, userActionService }, logger, authorization, @@ -50,9 +49,8 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P entities: Array.from(entities.values()), }); - const attachmentIds = await attachmentService.getAttachmentIdsForCases({ + const attachmentIds = await attachmentService.getter.getAttachmentIdsForCases({ caseIds: ids, - unsecuredSavedObjectsClient, }); const userActionIds = await userActionService.getUserActionIdsForCases(ids); diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index db836786b6db3..669efc96c960c 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -67,7 +67,6 @@ export const getCasesByAlertID = async ( services: { caseService, attachmentService }, logger, authorization, - unsecuredSavedObjectsClient, } = clientArgs; try { @@ -107,8 +106,7 @@ export const getCasesByAlertID = async ( return []; } - const commentStats = await attachmentService.getCaseCommentStats({ - unsecuredSavedObjectsClient, + const commentStats = await attachmentService.getter.getCaseCommentStats({ caseIds, }); diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 340a93dee2d4f..0cbeb7358646c 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -241,7 +241,6 @@ export const push = async ( }), attachmentService.bulkUpdate({ - unsecuredSavedObjectsClient, comments: comments.saved_objects .filter((comment) => comment.attributes.pushed_at == null) .map((comment) => ({ diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 57f54a745cf2d..b214b40e34701 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -170,10 +170,11 @@ export class CasesClientFactory { }): CasesServices { this.validateInitialization(); - const attachmentService = new AttachmentService( - this.logger, - this.options.persistableStateAttachmentTypeRegistry - ); + const attachmentService = new AttachmentService({ + log: this.logger, + persistableStateAttachmentTypeRegistry: this.options.persistableStateAttachmentTypeRegistry, + unsecuredSavedObjectsClient, + }); const caseService = new CasesService({ log: this.logger, diff --git a/x-pack/plugins/cases/server/client/metrics/actions/actions.ts b/x-pack/plugins/cases/server/client/metrics/actions/actions.ts index ce2f71553f274..fe56ac1f289c5 100644 --- a/x-pack/plugins/cases/server/client/metrics/actions/actions.ts +++ b/x-pack/plugins/cases/server/client/metrics/actions/actions.ts @@ -25,7 +25,6 @@ export class Actions extends SingleCaseAggregationHandler { public async compute(): Promise { const { - unsecuredSavedObjectsClient, authorization, services: { attachmentService }, logger, @@ -48,7 +47,6 @@ export class Actions extends SingleCaseAggregationHandler { }, {}); const response = await attachmentService.executeCaseActionsAggregations({ - unsecuredSavedObjectsClient, caseId: theCase.id, filter: authorizationFilter, aggregations, diff --git a/x-pack/plugins/cases/server/client/metrics/alerts/count.ts b/x-pack/plugins/cases/server/client/metrics/alerts/count.ts index f5f7e0d2be560..70344016910b7 100644 --- a/x-pack/plugins/cases/server/client/metrics/alerts/count.ts +++ b/x-pack/plugins/cases/server/client/metrics/alerts/count.ts @@ -18,7 +18,6 @@ export class AlertsCount extends SingleCaseBaseHandler { public async compute(): Promise { const { - unsecuredSavedObjectsClient, authorization, services: { attachmentService }, logger, @@ -38,7 +37,6 @@ export class AlertsCount extends SingleCaseBaseHandler { ); const alertsCount = await attachmentService.countAlertsAttachedToCase({ - unsecuredSavedObjectsClient, caseId: theCase.id, filter: authorizationFilter, }); diff --git a/x-pack/plugins/cases/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts index 0c2c57253ab9f..2c17182f49a07 100644 --- a/x-pack/plugins/cases/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -74,6 +74,7 @@ type AttachmentsSubClientMock = jest.Mocked; const createAttachmentsSubClientMock = (): AttachmentsSubClientMock => { return { + bulkGet: jest.fn(), add: jest.fn(), bulkCreate: jest.fn(), deleteAll: jest.fn(), @@ -90,9 +91,10 @@ type UserActionsSubClientMock = jest.Mocked; const createUserActionsSubClientMock = (): UserActionsSubClientMock => { return { + find: jest.fn(), getAll: jest.fn(), getConnectors: jest.fn(), - find: jest.fn(), + stats: jest.fn(), }; }; diff --git a/x-pack/plugins/cases/server/client/types.ts b/x-pack/plugins/cases/server/client/types.ts index 1ceca25fb2871..e052bb7f58550 100644 --- a/x-pack/plugins/cases/server/client/types.ts +++ b/x-pack/plugins/cases/server/client/types.ts @@ -6,13 +6,14 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { SavedObjectsClientContract, Logger } from '@kbn/core/server'; +import type { SavedObjectsClientContract, Logger, SavedObject } from '@kbn/core/server'; import type { ActionsClient } from '@kbn/actions-plugin/server'; import type { LensServerPluginSetup } from '@kbn/lens-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import type { IBasePath } from '@kbn/core-http-browser'; import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server'; import type { KueryNode } from '@kbn/es-query'; +import type { SavedObjectError } from '@kbn/core-saved-objects-common'; import type { CasesFindRequest, User } from '../../common/api'; import type { Authorization } from '../authorization/authorization'; import type { @@ -64,3 +65,5 @@ export type CasesFindQueryParams = Partial< 'tags' | 'reporters' | 'status' | 'severity' | 'owner' | 'from' | 'to' | 'assignees' > & { sortByField?: string; authorizationFilter?: KueryNode } >; + +export type SOWithErrors = Array & { error: SavedObjectError }>; diff --git a/x-pack/plugins/cases/server/client/user_actions/client.ts b/x-pack/plugins/cases/server/client/user_actions/client.ts index 7a091cb43a0d2..2676955719adb 100644 --- a/x-pack/plugins/cases/server/client/user_actions/client.ts +++ b/x-pack/plugins/cases/server/client/user_actions/client.ts @@ -7,12 +7,14 @@ import type { GetCaseConnectorsResponse, + CaseUserActionStatsResponse, UserActionFindResponse, CaseUserActionsDeprecatedResponse, } from '../../../common/api'; import type { CasesClientArgs } from '../types'; import { get } from './get'; import { getConnectors } from './connectors'; +import { getStats } from './stats'; import type { GetConnectorsRequest, UserActionFind, UserActionGet } from './types'; import { find } from './find'; import type { CasesClient } from '../client'; @@ -30,6 +32,11 @@ export interface UserActionsSubClient { * Retrieves all the connectors used within a given case */ getConnectors(params: GetConnectorsRequest): Promise; + + /** + * Retrieves the total of comments and user actions in a given case + */ + stats(params: UserActionGet): Promise; } /** @@ -43,6 +50,7 @@ export const createUserActionsSubClient = ( find: (params) => find(params, casesClient, clientArgs), getAll: (params) => get(params, clientArgs), getConnectors: (params) => getConnectors(params, clientArgs), + stats: (params) => getStats(params, casesClient, clientArgs), }; return Object.freeze(attachmentSubClient); diff --git a/x-pack/plugins/cases/server/client/user_actions/connectors.ts b/x-pack/plugins/cases/server/client/user_actions/connectors.ts index d3079d466e662..da949996d0fd7 100644 --- a/x-pack/plugins/cases/server/client/user_actions/connectors.ts +++ b/x-pack/plugins/cases/server/client/user_actions/connectors.ts @@ -15,6 +15,7 @@ import type { CaseConnector, CaseUserActionInjectedAttributes, CaseExternalServiceBasic, + GetCaseConnectorsPushDetails, } from '../../../common/api'; import { GetCaseConnectorsResponseRt } from '../../../common/api'; import { @@ -55,6 +56,7 @@ export const getConnectors = async ( connectors, latestUserAction, userActionService, + logger, }); return GetCaseConnectorsResponseRt.encode(results); @@ -121,23 +123,39 @@ const getConnectorsInfo = async ({ latestUserAction, actionsClient, userActionService, + logger, }: { caseId: string; connectors: CaseConnectorActivity[]; latestUserAction?: SavedObject; actionsClient: PublicMethodsOf; userActionService: CaseUserActionService; + logger: CasesClientArgs['logger']; }): Promise => { const connectorIds = connectors.map((connector) => connector.connectorId); const [pushInfo, actionConnectors] = await Promise.all([ getEnrichedPushInfo({ caseId, activity: connectors, userActionService }), - actionsClient.getBulk(connectorIds), + await getActionConnectors(actionsClient, logger, connectorIds), ]); return createConnectorInfoResult({ actionConnectors, connectors, pushInfo, latestUserAction }); }; +const getActionConnectors = async ( + actionsClient: PublicMethodsOf, + logger: CasesClientArgs['logger'], + ids: string[] +): Promise => { + try { + return await actionsClient.getBulk(ids); + } catch (error) { + // silent error and log it + logger.error(`Failed to retrieve action connectors in the get case connectors route: ${error}`); + return []; + } +}; + interface PushDetails { connectorId: string; externalService: CaseExternalServiceBasic; @@ -253,10 +271,12 @@ const createConnectorInfoResult = ({ latestUserAction?: SavedObject; }) => { const results: GetCaseConnectorsResponse = {}; + const actionConnectorsMap = new Map( + actionConnectors.map((actionConnector) => [actionConnector.id, { ...actionConnector }]) + ); - for (let i = 0; i < connectors.length; i++) { - const connectorDetails = actionConnectors[i]; - const aggregationConnector = connectors[i]; + for (const aggregationConnector of connectors) { + const connectorDetails = actionConnectorsMap.get(aggregationConnector.connectorId); const connector = getConnectorInfoFromSavedObject(aggregationConnector.fields); const latestUserActionCreatedAt = getDate(latestUserAction?.attributes.created_at); @@ -269,15 +289,15 @@ const createConnectorInfoResult = ({ latestUserActionDate: latestUserActionCreatedAt, }); + const pushDetails = convertEnrichedPushInfoToDetails(enrichedPushInfo); + results[connector.id] = { ...connector, - name: connectorDetails.name, + name: connectorDetails?.name ?? connector.name, push: { needsToBePushed, hasBeenPushed: hasBeenPushed(enrichedPushInfo), - externalService: enrichedPushInfo?.externalService, - latestUserActionPushDate: enrichedPushInfo?.latestPushDate.toISOString(), - oldestUserActionPushDate: enrichedPushInfo?.oldestPushDate.toISOString(), + ...(pushDetails && { details: pushDetails }), }, }; } @@ -319,3 +339,17 @@ const hasDataToPush = ({ const hasBeenPushed = (pushInfo: EnrichedPushInfo | undefined): boolean => { return pushInfo != null; }; + +const convertEnrichedPushInfoToDetails = ( + info: EnrichedPushInfo | undefined +): GetCaseConnectorsPushDetails | undefined => { + if (info == null) { + return; + } + + return { + latestUserActionPushDate: info.latestPushDate.toISOString(), + oldestUserActionPushDate: info.oldestPushDate.toISOString(), + externalService: info.externalService, + }; +}; diff --git a/x-pack/plugins/cases/server/client/user_actions/stats.ts b/x-pack/plugins/cases/server/client/user_actions/stats.ts new file mode 100644 index 0000000000000..b93f4a19cda20 --- /dev/null +++ b/x-pack/plugins/cases/server/client/user_actions/stats.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CaseUserActionStatsResponse } from '../../../common/api'; +import { CaseUserActionStatsResponseRt } from '../../../common/api'; +import { createCaseError } from '../../common/error'; +import type { CasesClientArgs } from '..'; +import type { UserActionGet } from './types'; +import type { CasesClient } from '../client'; + +export const getStats = async ( + { caseId }: UserActionGet, + casesClient: CasesClient, + clientArgs: CasesClientArgs +): Promise => { + const { + services: { userActionService }, + logger, + } = clientArgs; + + try { + await casesClient.cases.resolve({ id: caseId, includeComments: false }); + const totals = await userActionService.getCaseUserActionStats({ + caseId, + }); + + return CaseUserActionStatsResponseRt.encode(totals); + } catch (error) { + throw createCaseError({ + message: `Failed to retrieve user action stats for case id: ${caseId}: ${error}`, + error, + logger, + }); + } +}; diff --git a/x-pack/plugins/cases/server/common/models/case_with_comments.ts b/x-pack/plugins/cases/server/common/models/case_with_comments.ts index 62d6b44b613f8..4e134d873cbaf 100644 --- a/x-pack/plugins/cases/server/common/models/case_with_comments.ts +++ b/x-pack/plugins/cases/server/common/models/case_with_comments.ts @@ -95,8 +95,7 @@ export class CaseCommentModel { }; if (queryRestAttributes.type === CommentType.user && queryRestAttributes?.comment) { - const currentComment = (await this.params.services.attachmentService.get({ - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, + const currentComment = (await this.params.services.attachmentService.getter.get({ attachmentId: id, })) as SavedObject; @@ -110,7 +109,6 @@ export class CaseCommentModel { const [comment, commentableCase] = await Promise.all([ this.params.services.attachmentService.update({ - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, attachmentId: id, updatedAttributes: { ...queryRestAttributes, @@ -212,7 +210,6 @@ export class CaseCommentModel { const [comment, commentableCase] = await Promise.all([ this.params.services.attachmentService.create({ - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, attributes: transformNewComment({ createdDate, ...commentReq, @@ -276,7 +273,6 @@ export class CaseCommentModel { private async validateAlertsLimitOnCase(totalAlertsInReq: number) { const alertsValueCount = await this.params.services.attachmentService.valueCountAlertsAttachedToCase({ - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, caseId: this.caseInfo.id, }); @@ -410,7 +406,6 @@ export class CaseCommentModel { const [newlyCreatedAttachments, commentableCase] = await Promise.all([ this.params.services.attachmentService.bulkCreate({ - unsecuredSavedObjectsClient: this.params.unsecuredSavedObjectsClient, attachments: attachments.map(({ id, ...attachment }) => { return { attributes: transformNewComment({ diff --git a/x-pack/plugins/cases/server/routes/api/get_internal_routes.ts b/x-pack/plugins/cases/server/routes/api/get_internal_routes.ts index ca1f44706caf9..41d9904de01bd 100644 --- a/x-pack/plugins/cases/server/routes/api/get_internal_routes.ts +++ b/x-pack/plugins/cases/server/routes/api/get_internal_routes.ts @@ -7,10 +7,12 @@ import type { UserProfileService } from '../../services'; import { getConnectorsRoute } from './internal/get_connectors'; +import { getCaseUserActionStatsRoute } from './internal/get_case_user_actions_stats'; import { bulkCreateAttachmentsRoute } from './internal/bulk_create_attachments'; import { bulkGetCasesRoute } from './internal/bulk_get_cases'; import { suggestUserProfilesRoute } from './internal/suggest_user_profiles'; import type { CaseRoute } from './types'; +import { bulkGetAttachmentsRoute } from './internal/bulk_get_attachments'; export const getInternalRoutes = (userProfileService: UserProfileService) => [ @@ -18,4 +20,6 @@ export const getInternalRoutes = (userProfileService: UserProfileService) => suggestUserProfilesRoute(userProfileService), getConnectorsRoute, bulkGetCasesRoute, + getCaseUserActionStatsRoute, + bulkGetAttachmentsRoute, ] as CaseRoute[]; diff --git a/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts b/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts new file mode 100644 index 0000000000000..8f72a42b91b41 --- /dev/null +++ b/x-pack/plugins/cases/server/routes/api/internal/bulk_get_attachments.ts @@ -0,0 +1,44 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import type { BulkGetAttachmentsRequest } from '../../../../common/api'; + +import { INTERNAL_BULK_GET_ATTACHMENTS_URL } from '../../../../common/constants'; +import { createCaseError } from '../../../common/error'; +import { createCasesRoute } from '../create_cases_route'; +import { escapeHatch } from '../utils'; + +export const bulkGetAttachmentsRoute = createCasesRoute({ + method: 'post', + path: INTERNAL_BULK_GET_ATTACHMENTS_URL, + params: { + params: schema.object({ + case_id: schema.string(), + }), + body: escapeHatch, + }, + handler: async ({ context, request, response }) => { + try { + const caseContext = await context.cases; + const client = await caseContext.getCasesClient(); + const body = request.body as BulkGetAttachmentsRequest; + + return response.ok({ + body: await client.attachments.bulkGet({ + caseID: request.params.case_id, + attachmentIDs: body.ids, + }), + }); + } catch (error) { + throw createCaseError({ + message: `Failed to bulk get attachments in route case id: ${request.params.case_id}: ${error}`, + error, + }); + } + }, +}); diff --git a/x-pack/plugins/cases/server/routes/api/internal/get_case_user_actions_stats.ts b/x-pack/plugins/cases/server/routes/api/internal/get_case_user_actions_stats.ts new file mode 100644 index 0000000000000..58d92c5f9d729 --- /dev/null +++ b/x-pack/plugins/cases/server/routes/api/internal/get_case_user_actions_stats.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; +import { INTERNAL_GET_CASE_USER_ACTIONS_STATS_URL } from '../../../../common/constants'; +import { createCaseError } from '../../../common/error'; +import { createCasesRoute } from '../create_cases_route'; + +export const getCaseUserActionStatsRoute = createCasesRoute({ + method: 'get', + path: INTERNAL_GET_CASE_USER_ACTIONS_STATS_URL, + params: { + params: schema.object({ + case_id: schema.string(), + }), + }, + handler: async ({ context, request, response }) => { + try { + const casesContext = await context.cases; + const casesClient = await casesContext.getCasesClient(); + const caseId = request.params.case_id; + + return response.ok({ + body: await casesClient.userActions.stats({ caseId }), + }); + } catch (error) { + throw createCaseError({ + message: `Failed to retrieve stats in route case id: ${request.params.case_id}: ${error}`, + error, + }); + } + }, +}); diff --git a/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts b/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts index 2e898c23d94b7..0fe7b51385981 100644 --- a/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts +++ b/x-pack/plugins/cases/server/saved_object_types/import_export/export.ts @@ -12,7 +12,10 @@ import type { SavedObjectsClientContract, SavedObjectsExportTransformContext, } from '@kbn/core/server'; -import type { CaseUserActionAttributes, CommentAttributes } from '../../../common/api'; +import type { + CaseUserActionAttributesWithoutConnectorId, + CommentAttributesWithoutRefs, +} from '../../../common/api'; import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, @@ -34,7 +37,13 @@ export async function handleExport({ objects: Array>; coreSetup: CoreSetup; logger: Logger; -}): Promise>> { +}): Promise< + Array< + SavedObject< + ESCaseAttributes | CommentAttributesWithoutRefs | CaseUserActionAttributesWithoutConnectorId + > + > +> { try { if (objects.length <= 0) { return []; @@ -64,15 +73,17 @@ export async function handleExport({ async function getAttachmentsAndUserActionsForCases( savedObjectsClient: SavedObjectsClientContract, caseIds: string[] -): Promise>> { +): Promise< + Array> +> { const [attachments, userActions] = await Promise.all([ - getAssociatedObjects({ + getAssociatedObjects({ savedObjectsClient, caseIds, sortField: defaultSortField, type: CASE_COMMENT_SAVED_OBJECT, }), - getAssociatedObjects({ + getAssociatedObjects({ savedObjectsClient, caseIds, sortField: defaultSortField, diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts index e42d75817c65b..9d46e90bca17f 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.test.ts @@ -5,15 +5,17 @@ * 2.0. */ -import type { SavedObjectSanitizedDoc } from '@kbn/core/server'; +import type { SavedObjectSanitizedDoc, SavedObjectUnsanitizedDoc } from '@kbn/core/server'; import { ACTION_SAVED_OBJECT_TYPE } from '@kbn/actions-plugin/server'; +import type { CasesConfigureAttributes } from '../../../common/api'; import { ConnectorTypes } from '../../../common/api'; import { CASE_CONFIGURE_SAVED_OBJECT, SECURITY_SOLUTION_OWNER } from '../../../common/constants'; import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants'; import { getNoneCaseConnector } from '../../common/utils'; import type { ESCaseConnectorWithId } from '../../services/test_utils'; import type { ESCasesConfigureAttributes } from '../../services/configure/types'; -import { configureConnectorIdMigration } from './configuration'; +import type { UnsanitizedConfigureConnector } from './configuration'; +import { createConnectorAttributeMigration, configureConnectorIdMigration } from './configuration'; // eslint-disable-next-line @typescript-eslint/naming-convention const create_7_14_0_configSchema = (connector?: ESCaseConnectorWithId) => ({ @@ -38,7 +40,95 @@ const create_7_14_0_configSchema = (connector?: ESCaseConnectorWithId) => ({ }, }); +// eslint-disable-next-line @typescript-eslint/naming-convention +const create_7_9_0_configSchema = ( + overrides?: object +): SavedObjectUnsanitizedDoc => { + return { + attributes: { + connector_id: 'b35c26a9-4d0d-4ffb-be85-4e9b96266204', + connector_name: '', + closure_type: 'close-by-user', + created_at: '2023-01-31T22:11:49.480Z', + created_by: { + email: 'test@test.com', + full_name: 'tester', + username: 'tester_user', + }, + updated_at: null, + updated_by: null, + ...overrides, + }, + id: '1', + type: 'cases-configure', + references: [], + updated_at: '2023-01-31T22:00:50.003Z', + } as SavedObjectUnsanitizedDoc; +}; + describe('configuration migrations', () => { + describe('7.10.0 connector migration', () => { + it('creates the connector field with the connector id and name nested under it', () => { + const config = createConnectorAttributeMigration(create_7_9_0_configSchema()); + + expect(config.attributes.connector.id).toEqual('b35c26a9-4d0d-4ffb-be85-4e9b96266204'); + expect(config.attributes.connector.name).toEqual(''); + }); + + it('sets the connector.name field to the existing name', () => { + const config = createConnectorAttributeMigration( + create_7_9_0_configSchema({ connector_name: 'connector-name' }) + ); + + expect(config.attributes.connector.name).toEqual('connector-name'); + }); + + it('sets the connector.fields to null and connector.type to .none', () => { + const config = createConnectorAttributeMigration(create_7_9_0_configSchema()); + + expect(config.attributes.connector).toMatchInlineSnapshot(` + Object { + "fields": null, + "id": "b35c26a9-4d0d-4ffb-be85-4e9b96266204", + "name": "", + "type": ".none", + } + `); + }); + + it('does not modify the other attributes of the saved object', () => { + const config = createConnectorAttributeMigration(create_7_9_0_configSchema()); + + const configAttributes = config as SavedObjectSanitizedDoc; + expect(configAttributes.attributes.created_by.email).toEqual('test@test.com'); + }); + + it('sets name and id to none when they are undefined', () => { + const config = createConnectorAttributeMigration( + create_7_9_0_configSchema({ connector_name: undefined, connector_id: undefined }) + ); + + expect(config.attributes.connector.id).toEqual('none'); + expect(config.attributes.connector.name).toEqual('none'); + }); + + it('removes the connector_id and connector_name fields', () => { + const config = createConnectorAttributeMigration( + create_7_9_0_configSchema({ connector_name: 'name', connector_id: 'id' }) + ); + + expect(config.attributes).not.toHaveProperty('connector_id'); + expect(config.attributes).not.toHaveProperty('connector_name'); + }); + + it('sets the references to an empty array when it is initially undefined', () => { + const docWithoutRefs = { ...create_7_9_0_configSchema(), references: undefined }; + const config = createConnectorAttributeMigration(docWithoutRefs); + + expect(config.references).toEqual([]); + }); + }); + describe('7.15.0 connector ID migration', () => { it('does not create a reference when the connector ID is none', () => { const configureSavedObject = create_7_14_0_configSchema(getNoneCaseConnector()); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts index 84d1164ac23f1..b758d7ca0e7d3 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/configuration.ts @@ -14,7 +14,7 @@ import { addOwnerToSO } from '.'; import { CONNECTOR_ID_REFERENCE_NAME } from '../../common/constants'; import { transformConnectorIdToReference } from './user_actions/connector_id'; -interface UnsanitizedConfigureConnector { +export interface UnsanitizedConfigureConnector { connector_id: string; connector_name: string; } @@ -28,6 +28,26 @@ interface SanitizedConfigureConnector { }; } +export const createConnectorAttributeMigration = ( + doc: SavedObjectUnsanitizedDoc +): SavedObjectSanitizedDoc => { + const { connector_id, connector_name, ...restAttributes } = doc.attributes; + + return { + ...doc, + attributes: { + ...restAttributes, + connector: { + id: connector_id ?? 'none', + name: connector_name ?? 'none', + type: ConnectorTypes.none, + fields: null, + }, + }, + references: doc.references || [], + }; +}; + export const configureConnectorIdMigration = ( doc: SavedObjectUnsanitizedDoc<{ connector?: { id: string } }> ): SavedObjectSanitizedDoc => { @@ -50,25 +70,7 @@ export const configureConnectorIdMigration = ( }; export const configureMigrations = { - '7.10.0': ( - doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { - const { connector_id, connector_name, ...restAttributes } = doc.attributes; - - return { - ...doc, - attributes: { - ...restAttributes, - connector: { - id: connector_id ?? 'none', - name: connector_name ?? 'none', - type: ConnectorTypes.none, - fields: null, - }, - }, - references: doc.references || [], - }; - }, + '7.10.0': createConnectorAttributeMigration, '7.14.0': ( doc: SavedObjectUnsanitizedDoc> ): SavedObjectSanitizedDoc => { diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts index e152cb6d386b0..db14d05f05762 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/user_actions/index.ts @@ -18,7 +18,7 @@ import { mergeSavedObjectMigrationMaps } from '@kbn/core/server'; import type { MigrateFunctionsObject, MigrateFunction } from '@kbn/kibana-utils-plugin/common'; import { mapValues } from 'lodash'; import type { PersistableStateAttachmentState } from '../../../attachment_framework/types'; -import type { CaseUserActionAttributes } from '../../../../common/api'; +import type { CaseUserActionAttributesWithoutConnectorId } from '../../../../common/api'; import { ActionTypes, CommentType, ConnectorTypes } from '../../../../common/api'; import type { PersistableStateAttachmentTypeRegistry } from '../../../attachment_framework/persistable_state_registry'; import type { SanitizedCaseOwner } from '..'; @@ -40,7 +40,7 @@ export const createUserActionsMigrations = ( ): SavedObjectMigrationMap => { const persistableStateAttachmentMigrations = mapValues< MigrateFunctionsObject, - SavedObjectMigrationFn + SavedObjectMigrationFn >( getAllPersistableAttachmentMigrations(deps.persistableStateAttachmentTypeRegistry), migratePersistableStateAttachments @@ -105,8 +105,11 @@ export const createUserActionsMigrations = ( export const migratePersistableStateAttachments = ( migrate: MigrateFunction - ): SavedObjectMigrationFn => - (doc: SavedObjectUnsanitizedDoc) => { + ): SavedObjectMigrationFn< + CaseUserActionAttributesWithoutConnectorId, + CaseUserActionAttributesWithoutConnectorId + > => + (doc: SavedObjectUnsanitizedDoc) => { if ( doc.attributes.type !== ActionTypes.comment || doc.attributes.payload.comment.type !== CommentType.persistableState diff --git a/x-pack/plugins/cases/server/services/attachments/index.test.ts b/x-pack/plugins/cases/server/services/attachments/index.test.ts index e1dce7877a58d..17fa8baac72e5 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.test.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.test.ts @@ -28,7 +28,11 @@ describe('CasesService', () => { beforeEach(() => { jest.clearAllMocks(); - service = new AttachmentService(mockLogger, persistableStateAttachmentTypeRegistry); + service = new AttachmentService({ + log: mockLogger, + persistableStateAttachmentTypeRegistry, + unsecuredSavedObjectsClient, + }); }); describe('update', () => { @@ -44,7 +48,6 @@ describe('CasesService', () => { unsecuredSavedObjectsClient.update.mockResolvedValue(soClientRes); const res = await service.update({ - unsecuredSavedObjectsClient, attachmentId: '1', updatedAttributes: persistableStateAttachment, options: { references: [] }, @@ -60,7 +63,6 @@ describe('CasesService', () => { }); const res = await service.update({ - unsecuredSavedObjectsClient, attachmentId: '1', updatedAttributes: externalReferenceAttachmentSO, options: { references: [] }, @@ -76,7 +78,6 @@ describe('CasesService', () => { }); const res = await service.update({ - unsecuredSavedObjectsClient, attachmentId: '1', updatedAttributes: externalReferenceAttachmentES, options: { references: [] }, @@ -111,7 +112,6 @@ describe('CasesService', () => { }); const res = await service.bulkUpdate({ - unsecuredSavedObjectsClient, comments: [ { attachmentId: '1', diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index bda8ab9333b1c..b0192e8b1b8ee 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -6,33 +6,23 @@ */ import type { - Logger, SavedObject, SavedObjectReference, SavedObjectsBulkResponse, SavedObjectsBulkUpdateResponse, - SavedObjectsClientContract, SavedObjectsFindResponse, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, } from '@kbn/core/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { KueryNode } from '@kbn/es-query'; import type { - AttachmentTotals, - AttributesTypeAlerts, CommentAttributes as AttachmentAttributes, CommentAttributesWithoutRefs as AttachmentAttributesWithoutRefs, CommentPatchAttributes as AttachmentPatchAttributes, } from '../../../common/api'; import { CommentType } from '../../../common/api'; -import { - CASE_COMMENT_SAVED_OBJECT, - CASE_SAVED_OBJECT, - MAX_DOCS_PER_PAGE, -} from '../../../common/constants'; -import type { ClientArgs } from '..'; +import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../../common/constants'; import { buildFilter, combineFilters } from '../../client/utils'; import { defaultSortField } from '../../common/utils'; import type { AggregationResponse } from '../../client/metrics/types'; @@ -42,15 +32,10 @@ import { injectAttachmentSOAttributesFromRefsForPatch, } from '../so_references'; import type { SavedObjectFindOptionsKueryNode } from '../../common/types'; -import type { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry'; import type { IndexRefresh } from '../types'; +import type { AttachedToCaseArgs, GetAttachmentArgs, ServiceContext } from './types'; +import { AttachmentGetter } from './operations/get'; -interface AttachedToCaseArgs extends ClientArgs { - caseId: string; - filter?: KueryNode; -} - -type GetAllAlertsAttachToCaseArgs = AttachedToCaseArgs; type AlertsAttachedToCaseArgs = AttachedToCaseArgs; interface AttachmentsAttachedToCaseArgs extends AttachedToCaseArgs { @@ -62,19 +47,15 @@ interface CountActionsAttachedToCaseArgs extends AttachedToCaseArgs { aggregations: Record; } -interface GetAttachmentArgs extends ClientArgs { - attachmentId: string; -} - interface DeleteAttachmentArgs extends GetAttachmentArgs, IndexRefresh {} -interface CreateAttachmentArgs extends ClientArgs, IndexRefresh { +interface CreateAttachmentArgs extends IndexRefresh { attributes: AttachmentAttributes; references: SavedObjectReference[]; id: string; } -interface BulkCreateAttachments extends ClientArgs, IndexRefresh { +interface BulkCreateAttachments extends IndexRefresh { attachments: Array<{ attributes: AttachmentAttributes; references: SavedObjectReference[]; @@ -88,60 +69,28 @@ interface UpdateArgs { options?: Omit, 'upsert'>; } -export type UpdateAttachmentArgs = UpdateArgs & ClientArgs; +export type UpdateAttachmentArgs = UpdateArgs; -interface BulkUpdateAttachmentArgs extends ClientArgs, IndexRefresh { +interface BulkUpdateAttachmentArgs extends IndexRefresh { comments: UpdateArgs[]; } export class AttachmentService { - constructor( - private readonly log: Logger, - private readonly persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry - ) {} - - public async getAttachmentIdsForCases({ - caseIds, - unsecuredSavedObjectsClient, - }: { - caseIds: string[]; - unsecuredSavedObjectsClient: SavedObjectsClientContract; - }) { - try { - this.log.debug(`Attempting to retrieve attachments associated with cases: [${caseIds}]`); + private readonly _getter: AttachmentGetter; - const finder = unsecuredSavedObjectsClient.createPointInTimeFinder({ - type: CASE_COMMENT_SAVED_OBJECT, - hasReference: caseIds.map((id) => ({ id, type: CASE_SAVED_OBJECT })), - sortField: 'created_at', - sortOrder: 'asc', - /** - * We only care about the ids so to reduce the data returned we should limit the fields in the response. Core - * doesn't support retrieving no fields (id would always be returned anyway) so to limit it we'll only request - * the owner even though we don't need it. - */ - fields: ['owner'], - perPage: MAX_DOCS_PER_PAGE, - }); - - const ids: string[] = []; - - for await (const attachmentSavedObject of finder.find()) { - ids.push(...attachmentSavedObject.saved_objects.map((attachment) => attachment.id)); - } + constructor(private readonly context: ServiceContext) { + this._getter = new AttachmentGetter(context); + } - return ids; - } catch (error) { - this.log.error(`Error retrieving attachments associated with cases: [${caseIds}]: ${error}`); - throw error; - } + public get getter() { + return this._getter; } public async countAlertsAttachedToCase( params: AlertsAttachedToCaseArgs ): Promise { try { - this.log.debug(`Attempting to count alerts for case id ${params.caseId}`); + this.context.log.debug(`Attempting to count alerts for case id ${params.caseId}`); const res = await this.executeCaseAggregations<{ alerts: { value: number } }>({ ...params, attachmentType: CommentType.alert, @@ -150,7 +99,7 @@ export class AttachmentService { return res?.alerts?.value; } catch (error) { - this.log.error(`Error while counting alerts for case id ${params.caseId}: ${error}`); + this.context.log.error(`Error while counting alerts for case id ${params.caseId}: ${error}`); throw error; } } @@ -167,7 +116,7 @@ export class AttachmentService { public async valueCountAlertsAttachedToCase(params: AlertsAttachedToCaseArgs): Promise { try { - this.log.debug(`Attempting to value count alerts for case id ${params.caseId}`); + this.context.log.debug(`Attempting to value count alerts for case id ${params.caseId}`); const res = await this.executeCaseAggregations<{ alerts: { value: number } }>({ ...params, attachmentType: CommentType.alert, @@ -176,47 +125,9 @@ export class AttachmentService { return res?.alerts?.value ?? 0; } catch (error) { - this.log.error(`Error while value counting alerts for case id ${params.caseId}: ${error}`); - throw error; - } - } - - /** - * Retrieves all the alerts attached to a case. - */ - public async getAllAlertsAttachToCase({ - unsecuredSavedObjectsClient, - caseId, - filter, - }: GetAllAlertsAttachToCaseArgs): Promise>> { - try { - this.log.debug(`Attempting to GET all alerts for case id ${caseId}`); - const alertsFilter = buildFilter({ - filters: [CommentType.alert], - field: 'type', - operator: 'or', - type: CASE_COMMENT_SAVED_OBJECT, - }); - - const combinedFilter = combineFilters([alertsFilter, filter]); - - const finder = unsecuredSavedObjectsClient.createPointInTimeFinder({ - type: CASE_COMMENT_SAVED_OBJECT, - hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, - sortField: 'created_at', - sortOrder: 'asc', - filter: combinedFilter, - perPage: MAX_DOCS_PER_PAGE, - }); - - let result: Array> = []; - for await (const userActionSavedObject of finder.find()) { - result = result.concat(userActionSavedObject.saved_objects); - } - - return result; - } catch (error) { - this.log.error(`Error on GET all alerts for case id ${caseId}: ${error}`); + this.context.log.error( + `Error while value counting alerts for case id ${params.caseId}: ${error}` + ); throw error; } } @@ -225,14 +136,13 @@ export class AttachmentService { * Executes the aggregations against a type of attachment attached to a case. */ public async executeCaseAggregations({ - unsecuredSavedObjectsClient, caseId, filter, aggregations, attachmentType, }: AttachmentsAttachedToCaseArgs): Promise { try { - this.log.debug(`Attempting to aggregate for case id ${caseId}`); + this.context.log.debug(`Attempting to aggregate for case id ${caseId}`); const attachmentFilter = buildFilter({ filters: attachmentType, field: 'type', @@ -242,7 +152,10 @@ export class AttachmentService { const combinedFilter = combineFilters([attachmentFilter, filter]); - const response = await unsecuredSavedObjectsClient.find({ + const response = await this.context.unsecuredSavedObjectsClient.find< + AttachmentAttributes, + Agg + >({ type: CASE_COMMENT_SAVED_OBJECT, hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, page: 1, @@ -254,7 +167,7 @@ export class AttachmentService { return response.aggregations; } catch (error) { - this.log.error(`Error while executing aggregation for case id ${caseId}: ${error}`); + this.context.log.error(`Error while executing aggregation for case id ${caseId}: ${error}`); throw error; } } @@ -266,133 +179,114 @@ export class AttachmentService { params: CountActionsAttachedToCaseArgs ): Promise { try { - this.log.debug(`Attempting to count actions for case id ${params.caseId}`); + this.context.log.debug(`Attempting to count actions for case id ${params.caseId}`); return await this.executeCaseAggregations({ ...params, attachmentType: CommentType.actions }); } catch (error) { - this.log.error(`Error while counting actions for case id ${params.caseId}: ${error}`); + this.context.log.error(`Error while counting actions for case id ${params.caseId}: ${error}`); throw error; } } - public async get({ - unsecuredSavedObjectsClient, - attachmentId, - }: GetAttachmentArgs): Promise> { + public async delete({ attachmentId, refresh }: DeleteAttachmentArgs) { try { - this.log.debug(`Attempting to GET attachment ${attachmentId}`); - const res = await unsecuredSavedObjectsClient.get( + this.context.log.debug(`Attempting to DELETE attachment ${attachmentId}`); + return await this.context.unsecuredSavedObjectsClient.delete( CASE_COMMENT_SAVED_OBJECT, - attachmentId + attachmentId, + { + refresh, + } ); - - return injectAttachmentSOAttributesFromRefs(res, this.persistableStateAttachmentTypeRegistry); } catch (error) { - this.log.error(`Error on GET attachment ${attachmentId}: ${error}`); - throw error; - } - } - - public async delete({ - unsecuredSavedObjectsClient, - attachmentId, - refresh, - }: DeleteAttachmentArgs) { - try { - this.log.debug(`Attempting to DELETE attachment ${attachmentId}`); - return await unsecuredSavedObjectsClient.delete(CASE_COMMENT_SAVED_OBJECT, attachmentId, { - refresh, - }); - } catch (error) { - this.log.error(`Error on DELETE attachment ${attachmentId}: ${error}`); + this.context.log.error(`Error on DELETE attachment ${attachmentId}: ${error}`); throw error; } } public async create({ - unsecuredSavedObjectsClient, attributes, references, id, refresh, }: CreateAttachmentArgs): Promise> { try { - this.log.debug(`Attempting to POST a new comment`); + this.context.log.debug(`Attempting to POST a new comment`); const { attributes: extractedAttributes, references: extractedReferences } = extractAttachmentSORefsFromAttributes( attributes, references, - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); - const attachment = await unsecuredSavedObjectsClient.create( - CASE_COMMENT_SAVED_OBJECT, - extractedAttributes, - { - references: extractedReferences, - id, - refresh, - } - ); + const attachment = + await this.context.unsecuredSavedObjectsClient.create( + CASE_COMMENT_SAVED_OBJECT, + extractedAttributes, + { + references: extractedReferences, + id, + refresh, + } + ); return injectAttachmentSOAttributesFromRefs( attachment, - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); } catch (error) { - this.log.error(`Error on POST a new comment: ${error}`); + this.context.log.error(`Error on POST a new comment: ${error}`); throw error; } } public async bulkCreate({ - unsecuredSavedObjectsClient, attachments, refresh, }: BulkCreateAttachments): Promise> { try { - this.log.debug(`Attempting to bulk create attachments`); - const res = await unsecuredSavedObjectsClient.bulkCreate( - attachments.map((attachment) => { - const { attributes: extractedAttributes, references: extractedReferences } = - extractAttachmentSORefsFromAttributes( - attachment.attributes, - attachment.references, - this.persistableStateAttachmentTypeRegistry - ); - - return { - type: CASE_COMMENT_SAVED_OBJECT, - ...attachment, - attributes: extractedAttributes, - references: extractedReferences, - }; - }), - { refresh } - ); + this.context.log.debug(`Attempting to bulk create attachments`); + const res = + await this.context.unsecuredSavedObjectsClient.bulkCreate( + attachments.map((attachment) => { + const { attributes: extractedAttributes, references: extractedReferences } = + extractAttachmentSORefsFromAttributes( + attachment.attributes, + attachment.references, + this.context.persistableStateAttachmentTypeRegistry + ); + + return { + type: CASE_COMMENT_SAVED_OBJECT, + ...attachment, + attributes: extractedAttributes, + references: extractedReferences, + }; + }), + { refresh } + ); return { saved_objects: res.saved_objects.map((so) => { return injectAttachmentSOAttributesFromRefs( so, - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); }), }; } catch (error) { - this.log.error(`Error on bulk create attachments: ${error}`); + this.context.log.error(`Error on bulk create attachments: ${error}`); throw error; } } public async update({ - unsecuredSavedObjectsClient, attachmentId, updatedAttributes, options, }: UpdateAttachmentArgs): Promise> { try { - this.log.debug(`Attempting to UPDATE comment ${attachmentId}`); + this.context.log.debug(`Attempting to UPDATE comment ${attachmentId}`); const { attributes: extractedAttributes, @@ -401,165 +295,116 @@ export class AttachmentService { } = extractAttachmentSORefsFromAttributes( updatedAttributes, options?.references ?? [], - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); const shouldUpdateRefs = extractedReferences.length > 0 || didDeleteOperation; - const res = await unsecuredSavedObjectsClient.update( - CASE_COMMENT_SAVED_OBJECT, - attachmentId, - extractedAttributes, - { - ...options, - /** - * If options?.references are undefined and there is no field to move to the refs - * then the extractedReferences will be an empty array. If we pass the empty array - * on the update then all previously refs will be removed. The check below is needed - * to prevent this. - */ - references: shouldUpdateRefs ? extractedReferences : undefined, - } - ); + const res = + await this.context.unsecuredSavedObjectsClient.update( + CASE_COMMENT_SAVED_OBJECT, + attachmentId, + extractedAttributes, + { + ...options, + /** + * If options?.references are undefined and there is no field to move to the refs + * then the extractedReferences will be an empty array. If we pass the empty array + * on the update then all previously refs will be removed. The check below is needed + * to prevent this. + */ + references: shouldUpdateRefs ? extractedReferences : undefined, + } + ); return injectAttachmentSOAttributesFromRefsForPatch( updatedAttributes, res, - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); } catch (error) { - this.log.error(`Error on UPDATE comment ${attachmentId}: ${error}`); + this.context.log.error(`Error on UPDATE comment ${attachmentId}: ${error}`); throw error; } } public async bulkUpdate({ - unsecuredSavedObjectsClient, comments, refresh, }: BulkUpdateAttachmentArgs): Promise> { try { - this.log.debug( + this.context.log.debug( `Attempting to UPDATE comments ${comments.map((c) => c.attachmentId).join(', ')}` ); - const res = await unsecuredSavedObjectsClient.bulkUpdate( - comments.map((c) => { - const { - attributes: extractedAttributes, - references: extractedReferences, - didDeleteOperation, - } = extractAttachmentSORefsFromAttributes( - c.updatedAttributes, - c.options?.references ?? [], - this.persistableStateAttachmentTypeRegistry - ); - - const shouldUpdateRefs = extractedReferences.length > 0 || didDeleteOperation; + const res = + await this.context.unsecuredSavedObjectsClient.bulkUpdate( + comments.map((c) => { + const { + attributes: extractedAttributes, + references: extractedReferences, + didDeleteOperation, + } = extractAttachmentSORefsFromAttributes( + c.updatedAttributes, + c.options?.references ?? [], + this.context.persistableStateAttachmentTypeRegistry + ); - return { - ...c.options, - type: CASE_COMMENT_SAVED_OBJECT, - id: c.attachmentId, - attributes: extractedAttributes, - /* If c.options?.references are undefined and there is no field to move to the refs - * then the extractedAttributes will be an empty array. If we pass the empty array - * on the update then all previously refs will be removed. The check below is needed - * to prevent this. - */ - references: shouldUpdateRefs ? extractedReferences : undefined, - }; - }), - { refresh } - ); + const shouldUpdateRefs = extractedReferences.length > 0 || didDeleteOperation; + + return { + ...c.options, + type: CASE_COMMENT_SAVED_OBJECT, + id: c.attachmentId, + attributes: extractedAttributes, + /* If c.options?.references are undefined and there is no field to move to the refs + * then the extractedAttributes will be an empty array. If we pass the empty array + * on the update then all previously refs will be removed. The check below is needed + * to prevent this. + */ + references: shouldUpdateRefs ? extractedReferences : undefined, + }; + }), + { refresh } + ); return { saved_objects: res.saved_objects.map((so, index) => { return injectAttachmentSOAttributesFromRefsForPatch( comments[index].updatedAttributes, so, - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); }), }; } catch (error) { - this.log.error( + this.context.log.error( `Error on UPDATE comments ${comments.map((c) => c.attachmentId).join(', ')}: ${error}` ); throw error; } } - public async getCaseCommentStats({ - unsecuredSavedObjectsClient, - caseIds, - }: { - unsecuredSavedObjectsClient: SavedObjectsClientContract; - caseIds: string[]; - }): Promise> { - if (caseIds.length <= 0) { - return new Map(); - } - - interface AggsResult { - references: { - caseIds: { - buckets: Array<{ - key: string; - doc_count: number; - reverse: { - alerts: { - value: number; - }; - comments: { - doc_count: number; - }; - }; - }>; - }; - }; - } - - const res = await unsecuredSavedObjectsClient.find({ - hasReference: caseIds.map((id) => ({ type: CASE_SAVED_OBJECT, id })), - hasReferenceOperator: 'OR', - type: CASE_COMMENT_SAVED_OBJECT, - perPage: 0, - aggs: AttachmentService.buildCommentStatsAggs(caseIds), - }); - - return ( - res.aggregations?.references.caseIds.buckets.reduce((acc, idBucket) => { - acc.set(idBucket.key, { - userComments: idBucket.reverse.comments.doc_count, - alerts: idBucket.reverse.alerts.value, - }); - return acc; - }, new Map()) ?? new Map() - ); - } - public async find({ - unsecuredSavedObjectsClient, options, }: { - unsecuredSavedObjectsClient: SavedObjectsClientContract; options?: SavedObjectFindOptionsKueryNode; }): Promise> { try { - this.log.debug(`Attempting to find comments`); - const res = await unsecuredSavedObjectsClient.find({ - sortField: defaultSortField, - ...options, - type: CASE_COMMENT_SAVED_OBJECT, - }); + this.context.log.debug(`Attempting to find comments`); + const res = + await this.context.unsecuredSavedObjectsClient.find({ + sortField: defaultSortField, + ...options, + type: CASE_COMMENT_SAVED_OBJECT, + }); return { ...res, saved_objects: res.saved_objects.map((so) => { const injectedSO = injectAttachmentSOAttributesFromRefs( so, - this.persistableStateAttachmentTypeRegistry + this.context.persistableStateAttachmentTypeRegistry ); return { @@ -569,47 +414,8 @@ export class AttachmentService { }), }; } catch (error) { - this.log.error(`Error on find comments: ${error}`); + this.context.log.error(`Error on find comments: ${error}`); throw error; } } - - private static buildCommentStatsAggs( - ids: string[] - ): Record { - return { - references: { - nested: { - path: `${CASE_COMMENT_SAVED_OBJECT}.references`, - }, - aggregations: { - caseIds: { - terms: { - field: `${CASE_COMMENT_SAVED_OBJECT}.references.id`, - size: ids.length, - }, - aggregations: { - reverse: { - reverse_nested: {}, - aggregations: { - alerts: { - cardinality: { - field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.alertId`, - }, - }, - comments: { - filter: { - term: { - [`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`]: CommentType.user, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }; - } } diff --git a/x-pack/plugins/cases/server/services/attachments/operations/get.ts b/x-pack/plugins/cases/server/services/attachments/operations/get.ts new file mode 100644 index 0000000000000..89fa8bbb12277 --- /dev/null +++ b/x-pack/plugins/cases/server/services/attachments/operations/get.ts @@ -0,0 +1,250 @@ +/* + * 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 { SavedObject } from '@kbn/core/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + CASE_COMMENT_SAVED_OBJECT, + CASE_SAVED_OBJECT, + MAX_DOCS_PER_PAGE, +} from '../../../../common/constants'; +import { buildFilter, combineFilters } from '../../../client/utils'; +import type { + AttachmentTotals, + AttributesTypeAlerts, + CommentAttributes as AttachmentAttributes, + CommentAttributesWithoutRefs as AttachmentAttributesWithoutRefs, + CommentAttributes, +} from '../../../../common/api'; +import { CommentType } from '../../../../common/api'; +import type { + AttachedToCaseArgs, + BulkOptionalAttributes, + GetAttachmentArgs, + ServiceContext, +} from '../types'; +import { + injectAttachmentAttributesAndHandleErrors, + injectAttachmentSOAttributesFromRefs, +} from '../../so_references'; + +type GetAllAlertsAttachToCaseArgs = AttachedToCaseArgs; + +export class AttachmentGetter { + constructor(private readonly context: ServiceContext) {} + + public async bulkGet( + attachmentIds: string[] + ): Promise> { + try { + this.context.log.debug( + `Attempting to retrieve attachments with ids: ${attachmentIds.join()}` + ); + + const response = + await this.context.unsecuredSavedObjectsClient.bulkGet( + attachmentIds.map((id) => ({ id, type: CASE_COMMENT_SAVED_OBJECT })) + ); + + return { + saved_objects: response.saved_objects.map((so) => + injectAttachmentAttributesAndHandleErrors( + so, + this.context.persistableStateAttachmentTypeRegistry + ) + ), + }; + } catch (error) { + this.context.log.error( + `Error retrieving attachments with ids ${attachmentIds.join()}: ${error}` + ); + throw error; + } + } + + public async getAttachmentIdsForCases({ caseIds }: { caseIds: string[] }) { + try { + this.context.log.debug( + `Attempting to retrieve attachments associated with cases: [${caseIds}]` + ); + + const finder = this.context.unsecuredSavedObjectsClient.createPointInTimeFinder({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: caseIds.map((id) => ({ id, type: CASE_SAVED_OBJECT })), + sortField: 'created_at', + sortOrder: 'asc', + /** + * We only care about the ids so to reduce the data returned we should limit the fields in the response. Core + * doesn't support retrieving no fields (id would always be returned anyway) so to limit it we'll only request + * the owner even though we don't need it. + */ + fields: ['owner'], + perPage: MAX_DOCS_PER_PAGE, + }); + + const ids: string[] = []; + + for await (const attachmentSavedObject of finder.find()) { + ids.push(...attachmentSavedObject.saved_objects.map((attachment) => attachment.id)); + } + + return ids; + } catch (error) { + this.context.log.error( + `Error retrieving attachments associated with cases: [${caseIds}]: ${error}` + ); + throw error; + } + } + + /** + * Retrieves all the alerts attached to a case. + */ + public async getAllAlertsAttachToCase({ + caseId, + filter, + }: GetAllAlertsAttachToCaseArgs): Promise>> { + try { + this.context.log.debug(`Attempting to GET all alerts for case id ${caseId}`); + const alertsFilter = buildFilter({ + filters: [CommentType.alert], + field: 'type', + operator: 'or', + type: CASE_COMMENT_SAVED_OBJECT, + }); + + const combinedFilter = combineFilters([alertsFilter, filter]); + + const finder = + this.context.unsecuredSavedObjectsClient.createPointInTimeFinder({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + sortField: 'created_at', + sortOrder: 'asc', + filter: combinedFilter, + perPage: MAX_DOCS_PER_PAGE, + }); + + let result: Array> = []; + for await (const userActionSavedObject of finder.find()) { + result = result.concat(userActionSavedObject.saved_objects); + } + + return result; + } catch (error) { + this.context.log.error(`Error on GET all alerts for case id ${caseId}: ${error}`); + throw error; + } + } + + public async get({ + attachmentId, + }: GetAttachmentArgs): Promise> { + try { + this.context.log.debug(`Attempting to GET attachment ${attachmentId}`); + const res = + await this.context.unsecuredSavedObjectsClient.get( + CASE_COMMENT_SAVED_OBJECT, + attachmentId + ); + + return injectAttachmentSOAttributesFromRefs( + res, + this.context.persistableStateAttachmentTypeRegistry + ); + } catch (error) { + this.context.log.error(`Error on GET attachment ${attachmentId}: ${error}`); + throw error; + } + } + + public async getCaseCommentStats({ + caseIds, + }: { + caseIds: string[]; + }): Promise> { + if (caseIds.length <= 0) { + return new Map(); + } + + interface AggsResult { + references: { + caseIds: { + buckets: Array<{ + key: string; + doc_count: number; + reverse: { + alerts: { + value: number; + }; + comments: { + doc_count: number; + }; + }; + }>; + }; + }; + } + + const res = await this.context.unsecuredSavedObjectsClient.find({ + hasReference: caseIds.map((id) => ({ type: CASE_SAVED_OBJECT, id })), + hasReferenceOperator: 'OR', + type: CASE_COMMENT_SAVED_OBJECT, + perPage: 0, + aggs: AttachmentGetter.buildCommentStatsAggs(caseIds), + }); + + return ( + res.aggregations?.references.caseIds.buckets.reduce((acc, idBucket) => { + acc.set(idBucket.key, { + userComments: idBucket.reverse.comments.doc_count, + alerts: idBucket.reverse.alerts.value, + }); + return acc; + }, new Map()) ?? new Map() + ); + } + + private static buildCommentStatsAggs( + ids: string[] + ): Record { + return { + references: { + nested: { + path: `${CASE_COMMENT_SAVED_OBJECT}.references`, + }, + aggregations: { + caseIds: { + terms: { + field: `${CASE_COMMENT_SAVED_OBJECT}.references.id`, + size: ids.length, + }, + aggregations: { + reverse: { + reverse_nested: {}, + aggregations: { + alerts: { + cardinality: { + field: `${CASE_COMMENT_SAVED_OBJECT}.attributes.alertId`, + }, + }, + comments: { + filter: { + term: { + [`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`]: CommentType.user, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + } +} diff --git a/x-pack/plugins/cases/server/services/attachments/types.ts b/x-pack/plugins/cases/server/services/attachments/types.ts new file mode 100644 index 0000000000000..554ba8afb049b --- /dev/null +++ b/x-pack/plugins/cases/server/services/attachments/types.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 type { + Logger, + SavedObject, + SavedObjectsBulkResponse, + SavedObjectsClientContract, +} from '@kbn/core/server'; +import type { KueryNode } from '@kbn/es-query'; +import type { PersistableStateAttachmentTypeRegistry } from '../../attachment_framework/persistable_state_registry'; +import type { PartialField } from '../../types'; + +export interface ServiceContext { + log: Logger; + persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry; + unsecuredSavedObjectsClient: SavedObjectsClientContract; +} + +export interface AttachedToCaseArgs { + caseId: string; + filter?: KueryNode; +} + +export interface GetAttachmentArgs { + attachmentId: string; +} + +export type OptionalAttributes = PartialField, 'attributes'>; + +export interface BulkOptionalAttributes + extends Omit, 'saved_objects'> { + saved_objects: Array>; +} diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts index c00407ec1a965..c84d4df964157 100644 --- a/x-pack/plugins/cases/server/services/cases/index.test.ts +++ b/x-pack/plugins/cases/server/services/cases/index.test.ts @@ -153,10 +153,11 @@ describe('CasesService', () => { const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const mockLogger = loggerMock.create(); const persistableStateAttachmentTypeRegistry = new PersistableStateAttachmentTypeRegistry(); - const attachmentService = new AttachmentService( - mockLogger, - persistableStateAttachmentTypeRegistry - ); + const attachmentService = new AttachmentService({ + log: mockLogger, + persistableStateAttachmentTypeRegistry, + unsecuredSavedObjectsClient, + }); let service: CasesService; diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 661f4cda00d7a..bc694a44d93eb 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -226,8 +226,7 @@ export class CasesService { return accMap; }, new Map>()); - const commentTotals = await this.attachmentService.getCaseCommentStats({ - unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, + const commentTotals = await this.attachmentService.getter.getCaseCommentStats({ caseIds: Array.from(casesMap.keys()), }); @@ -411,7 +410,6 @@ export class CasesService { this.log.debug(`Attempting to GET all comments internal for id ${JSON.stringify(id)}`); if (options?.page !== undefined || options?.perPage !== undefined) { return this.attachmentService.find({ - unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, options: { sortField: defaultSortField, ...options, @@ -420,7 +418,6 @@ export class CasesService { } return this.attachmentService.find({ - unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, options: { page: 1, perPage: MAX_DOCS_PER_PAGE, @@ -521,7 +518,6 @@ export class CasesService { username, full_name: user.full_name ?? null, email: user.email ?? null, - // TODO: verify that adding a new field is ok, shouldn't be a breaking change profile_uid: user.profile_uid, }; }) ?? [] diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index 7b2adbd1094f3..ee65ce172c596 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -14,6 +14,7 @@ import type { ConnectorMappingsService, AttachmentService, } from '.'; +import type { AttachmentGetter } from './attachments/operations/get'; import type { LicensingService } from './licensing'; import type { EmailNotificationService } from './notifications/email_notification_service'; import type { UserActionPersister } from './user_actions/operations/create'; @@ -24,6 +25,12 @@ interface UserActionServiceOperations { finder: CaseUserActionFinderServiceMock; } +interface AttachmentServiceOperations { + getter: AttachmentGetterServiceMock; +} + +export type AttachmentGetterServiceMock = jest.Mocked; + export type CaseServiceMock = jest.Mocked; export type CaseConfigureServiceMock = jest.Mocked; export type ConnectorMappingsServiceMock = jest.Mocked; @@ -33,7 +40,7 @@ export type CaseUserActionServiceMock = jest.Mocked< export type CaseUserActionPersisterServiceMock = jest.Mocked; export type CaseUserActionFinderServiceMock = jest.Mocked; export type AlertServiceMock = jest.Mocked; -export type AttachmentServiceMock = jest.Mocked; +export type AttachmentServiceMock = jest.Mocked; export type LicensingServiceMock = jest.Mocked; export type NotificationServiceMock = jest.Mocked; @@ -117,6 +124,7 @@ export const createUserActionServiceMock = (): CaseUserActionServiceMock => { getAll: jest.fn(), getUniqueConnectors: jest.fn(), getUserActionIdsForCases: jest.fn(), + getCaseUserActionStats: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error @@ -134,22 +142,33 @@ export const createAlertServiceMock = (): AlertServiceMock => { return service as unknown as AlertServiceMock; }; -export const createAttachmentServiceMock = (): AttachmentServiceMock => { - const service: PublicMethodsOf = { +const createAttachmentGetterServiceMock = (): AttachmentGetterServiceMock => { + const service: PublicMethodsOf = { get: jest.fn(), + bulkGet: jest.fn(), + getAllAlertsAttachToCase: jest.fn(), + getCaseCommentStats: jest.fn(), + getAttachmentIdsForCases: jest.fn(), + }; + + return service as unknown as AttachmentGetterServiceMock; +}; + +type FakeAttachmentService = PublicMethodsOf & AttachmentServiceOperations; + +export const createAttachmentServiceMock = (): AttachmentServiceMock => { + const service: FakeAttachmentService = { + getter: createAttachmentGetterServiceMock(), delete: jest.fn(), create: jest.fn(), bulkCreate: jest.fn(), update: jest.fn(), bulkUpdate: jest.fn(), find: jest.fn(), - getAllAlertsAttachToCase: jest.fn(), countAlertsAttachedToCase: jest.fn(), executeCaseActionsAggregations: jest.fn(), - getCaseCommentStats: jest.fn(), valueCountAlertsAttachedToCase: jest.fn(), executeCaseAggregations: jest.fn(), - getAttachmentIdsForCases: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error diff --git a/x-pack/plugins/cases/server/services/so_references.ts b/x-pack/plugins/cases/server/services/so_references.ts index d5dcc50da03e3..f85c4cab1829d 100644 --- a/x-pack/plugins/cases/server/services/so_references.ts +++ b/x-pack/plugins/cases/server/services/so_references.ts @@ -22,6 +22,7 @@ import { } from '../attachment_framework/so_references'; import { EXTERNAL_REFERENCE_REF_NAME } from '../common/constants'; import { isCommentRequestTypeExternalReferenceSO } from '../common/utils'; +import type { PartialField } from '../types'; import { SOReferenceExtractor } from './so_reference_extractor'; export const getAttachmentSOExtractor = (attachment: Partial) => { @@ -38,6 +39,30 @@ export const getAttachmentSOExtractor = (attachment: Partial) => return new SOReferenceExtractor(fieldsToExtract); }; +type OptionalAttributes = PartialField, 'attributes'>; + +/** + * This function should be used when the attributes field could be undefined. Specifically when + * performing a bulkGet within the core saved object library. If one of the requested ids does not exist in elasticsearch + * then the error field will be set and attributes will be undefined. + */ +export const injectAttachmentAttributesAndHandleErrors = ( + savedObject: OptionalAttributes, + persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry +): OptionalAttributes => { + if (!hasAttributes(savedObject)) { + // we don't actually have an attributes field here so the type doesn't matter, this cast is to get the types to stop + // complaining though + return savedObject as OptionalAttributes; + } + + return injectAttachmentSOAttributesFromRefs(savedObject, persistableStateAttachmentTypeRegistry); +}; + +const hasAttributes = (savedObject: OptionalAttributes): savedObject is SavedObject => { + return savedObject.error == null && savedObject.attributes != null; +}; + export const injectAttachmentSOAttributesFromRefs = ( savedObject: SavedObject, persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry diff --git a/x-pack/plugins/cases/server/services/user_actions/index.ts b/x-pack/plugins/cases/server/services/user_actions/index.ts index a96f113305444..159cb40a5f1f8 100644 --- a/x-pack/plugins/cases/server/services/user_actions/index.ts +++ b/x-pack/plugins/cases/server/services/user_actions/index.ts @@ -88,6 +88,16 @@ interface ConnectorFieldsBeforePushAggsResult { }; } +interface UserActionsStatsAggsResult { + total: number; + totals: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; +} + export class CaseUserActionService { private readonly _creator: UserActionPersister; private readonly _finder: UserActionFinder; @@ -661,4 +671,48 @@ export class CaseUserActionService { }, }; } + + public async getCaseUserActionStats({ caseId }: { caseId: string }) { + const response = await this.context.unsecuredSavedObjectsClient.find< + CaseUserActionAttributesWithoutConnectorId, + UserActionsStatsAggsResult + >({ + type: CASE_USER_ACTION_SAVED_OBJECT, + hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + page: 1, + perPage: 1, + sortField: defaultSortField, + aggs: CaseUserActionService.buildUserActionStatsAgg(), + }); + + const result = { + total: response.total, + total_comments: 0, + total_other_actions: 0, + }; + + response.aggregations?.totals.buckets.forEach(({ key, doc_count: docCount }) => { + if (key === 'user') { + result.total_comments = docCount; + } + }); + + result.total_other_actions = result.total - result.total_comments; + + return result; + } + + private static buildUserActionStatsAgg(): Record< + string, + estypes.AggregationsAggregationContainer + > { + return { + totals: { + terms: { + field: `${CASE_USER_ACTION_SAVED_OBJECT}.attributes.payload.comment.type`, + size: 100, + }, + }, + }; + } } diff --git a/x-pack/plugins/cases/server/services/user_actions/test_utils.ts b/x-pack/plugins/cases/server/services/user_actions/test_utils.ts index 335905cb116ce..49d47b13f8d04 100644 --- a/x-pack/plugins/cases/server/services/user_actions/test_utils.ts +++ b/x-pack/plugins/cases/server/services/user_actions/test_utils.ts @@ -19,7 +19,7 @@ import { SECURITY_SOLUTION_OWNER, } from '../../../common/constants'; import type { - CaseUserActionAttributes, + CaseUserActionAttributesWithoutConnectorId, ConnectorUserAction, UserAction, } from '../../../common/api'; @@ -45,15 +45,15 @@ import type { PersistableStateAttachmentTypeRegistry } from '../../attachment_fr import { transformFindResponseToExternalModel } from './transform'; export const createUserActionFindSO = ( - userAction: SavedObject -): SavedObjectsFindResult => ({ + userAction: SavedObject +): SavedObjectsFindResult => ({ ...userAction, score: 0, }); export const createConnectorUserAction = ( - overrides?: Partial -): SavedObject => { + overrides?: Partial +): SavedObject => { const { id, ...restConnector } = createConnectorObject().connector; return { ...createUserActionSO({ @@ -79,12 +79,12 @@ export const createUserActionSO = ({ action: UserAction; type?: string; payload?: Record; - attributesOverrides?: Partial; + attributesOverrides?: Partial; commentId?: string; connectorId?: string; pushedConnectorId?: string; references?: SavedObjectReference[]; -}): SavedObject => { +}): SavedObject => { const defaultParams = { action, created_at: 'abc', @@ -140,14 +140,14 @@ export const createUserActionSO = ({ ] : []), ], - } as SavedObject; + } as SavedObject; }; export const updateConnectorUserAction = ({ overrides, }: { - overrides?: Partial; -} = {}): SavedObject => { + overrides?: Partial; +} = {}): SavedObject => { const { id, ...restConnector } = createJiraConnector(); return { ...createUserActionSO({ @@ -163,8 +163,8 @@ export const updateConnectorUserAction = ({ export const pushConnectorUserAction = ({ overrides, }: { - overrides?: Partial; -} = {}): SavedObject => { + overrides?: Partial; +} = {}): SavedObject => { const { connector_id: connectorId, ...restExternalService } = createExternalService(); return { ...createUserActionSO({ @@ -177,7 +177,7 @@ export const pushConnectorUserAction = ({ }; }; -export const createCaseUserAction = (): SavedObject => { +export const createCaseUserAction = (): SavedObject => { const { id, ...restConnector } = createJiraConnector(); return { ...createUserActionSO({ @@ -231,7 +231,7 @@ export const createExternalReferenceUserAction = () => { export const testConnectorId = ( persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry, - userAction: SavedObject, + userAction: SavedObject, path: string, expectedConnectorId = '1' ) => { @@ -252,7 +252,9 @@ export const testConnectorId = ( }; const transformed = transformFindResponseToExternalModel( createSOFindResponse([ - createUserActionFindSO(invalidUserAction as SavedObject), + createUserActionFindSO( + invalidUserAction as SavedObject + ), ]), persistableStateAttachmentTypeRegistry ); @@ -267,7 +269,9 @@ export const testConnectorId = ( }; const transformed = transformFindResponseToExternalModel( createSOFindResponse([ - createUserActionFindSO(invalidUserAction as SavedObject), + createUserActionFindSO( + invalidUserAction as SavedObject + ), ]), persistableStateAttachmentTypeRegistry ) as SavedObjectsFindResponse; diff --git a/x-pack/plugins/cases/server/services/user_actions/transform.ts b/x-pack/plugins/cases/server/services/user_actions/transform.ts index 4af9873c5cc5b..9d7225660fbe4 100644 --- a/x-pack/plugins/cases/server/services/user_actions/transform.ts +++ b/x-pack/plugins/cases/server/services/user_actions/transform.ts @@ -113,7 +113,7 @@ function legacyTransformToExternalModel( } const addReferenceIdToPayload = ( - userAction: SavedObject, + userAction: SavedObject, persistableStateAttachmentTypeRegistry: PersistableStateAttachmentTypeRegistry ): CaseUserActionAttributes['payload'] => { const connectorId = getConnectorIdFromReferences(userAction); @@ -175,7 +175,7 @@ const addReferenceIdToPayload = ( }; function getConnectorIdFromReferences( - userAction: SavedObject + userAction: SavedObject ): string | null { const { references } = userAction; diff --git a/x-pack/plugins/cases/server/types.ts b/x-pack/plugins/cases/server/types.ts index 3c92a9a16cdbc..ba6d64dc88668 100644 --- a/x-pack/plugins/cases/server/types.ts +++ b/x-pack/plugins/cases/server/types.ts @@ -56,3 +56,5 @@ export interface PluginStartContract { export interface PluginSetupContract { attachmentFramework: AttachmentFramework; } + +export type PartialField = Omit & Partial>; diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts index 53e619b0d50be..15bcfdc53512f 100644 --- a/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @@ -18,7 +18,11 @@ const configSchema = schema.object({ schema.maybe(schema.string()) ), eventTypesAllowlist: schema.arrayOf(schema.string(), { - defaultValue: ['Loaded Kibana'], + defaultValue: [ + 'Loaded Kibana', // Sent once per page refresh (potentially, once per session) + 'Hosts View Query Submitted', // Worst-case scenario 1 every 2 seconds + 'Host Entry Clicked', // Worst-case scenario once per second - AT RISK, + ], }), }); diff --git a/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule_template_metadata.ts b/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule_template_metadata.ts index 0711bd371828a..567c763f5d845 100644 --- a/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule_template_metadata.ts +++ b/x-pack/plugins/cloud_security_posture/common/schemas/csp_rule_template_metadata.ts @@ -35,6 +35,7 @@ export const cspRuleTemplateMetadataSchemaV870 = rt.object({ id: rt.string(), version: rt.string(), rule_number: rt.maybe(rt.string()), + posture_type: rt.maybe(rt.string()), }), default_value: rt.maybe(rt.string()), description: rt.string(), diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.test.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.test.ts new file mode 100644 index 0000000000000..616fa533e7c52 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.test.ts @@ -0,0 +1,72 @@ +/* + * 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, act } from '@testing-library/react-hooks/dom'; +import { useNavigateFindings, useNavigateFindingsByResource } from './use_navigate_findings'; +import { useHistory } from 'react-router-dom'; + +jest.mock('react-router-dom', () => ({ + useHistory: jest.fn().mockReturnValue({ push: jest.fn() }), +})); + +jest.mock('./use_kibana', () => ({ + useKibana: jest.fn().mockReturnValue({ + services: { + data: { + query: { + queryString: { + getDefaultQuery: jest.fn().mockReturnValue({ + language: 'kuery', + query: '', + }), + }, + }, + }, + }, + }), +})); + +describe('useNavigateFindings', () => { + it('creates a URL to findings page with correct path and filter', () => { + const push = jest.fn(); + (useHistory as jest.Mock).mockReturnValueOnce({ push }); + + const filter = { foo: 1 }; + + const { result } = renderHook(() => useNavigateFindings()); + + act(() => { + result.current({ filter }); + }); + + expect(push).toHaveBeenCalledWith({ + pathname: '/cloud_security_posture/findings/default', + search: + "cspq=(filters:!((meta:(alias:!n,disabled:!f,key:filter,negate:!f,params:(query:(foo:1)),type:phrase),query:(match_phrase:(filter:(foo:1))))),query:(language:kuery,query:''))", + }); + expect(push).toHaveBeenCalledTimes(1); + }); + it('creates a URL to findings resource page with correct path and filter', () => { + const push = jest.fn(); + (useHistory as jest.Mock).mockReturnValueOnce({ push }); + + const filter = { foo: 1 }; + + const { result } = renderHook(() => useNavigateFindingsByResource()); + + act(() => { + result.current({ filter }); + }); + + expect(push).toHaveBeenCalledWith({ + pathname: '/cloud_security_posture/findings/resource', + search: + "cspq=(filters:!((meta:(alias:!n,disabled:!f,key:filter,negate:!f,params:(query:(foo:1)),type:phrase),query:(match_phrase:(filter:(foo:1))))),query:(language:kuery,query:''))", + }); + expect(push).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts index b9a408116b0b6..946941b181292 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_navigate_findings.ts @@ -5,61 +5,46 @@ * 2.0. */ +import { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { Query } from '@kbn/es-query'; +import { Filter } from '@kbn/es-query'; import { findingsNavigation } from '../navigation/constants'; import { encodeQuery } from '../navigation/query_utils'; -import { FindingsBaseURLQuery } from '../../pages/findings/types'; +import { useKibana } from './use_kibana'; -const getFindingsQuery = (queryValue: Query['query']): Pick => { - const query = - typeof queryValue === 'string' - ? queryValue - : // TODO: use a tested query builder instead ASAP - Object.entries(queryValue) - .reduce((a, [key, value]) => { - a.push(`${key}: "${value}"`); - return a; - }, []) - .join(' and '); +const createFilter = (key: string, value: string, negate = false): Filter => ({ + meta: { + alias: null, + negate, + disabled: false, + type: 'phrase', + key, + params: { query: value }, + }, + query: { match_phrase: { [key]: value } }, +}); - return { - query: { - language: 'kuery', - // NOTE: a query object is valid TS but throws on runtime - query, - }, - }!; -}; - -export const useNavigateFindings = () => { +const useNavigate = (pathname: string) => { const history = useHistory(); + const { services } = useKibana(); - return (query: Query['query'] = {}) => { - history.push({ - pathname: findingsNavigation.findings_default.path, - ...(query && { + return useCallback( + (filterParams: Record = {}) => { + const filters = Object.entries(filterParams).map(([key, value]) => createFilter(key, value)); + history.push({ + pathname, search: encodeQuery({ - ...getFindingsQuery(query), - filters: [], + // Set query language from user's preference + query: services.data.query.queryString.getDefaultQuery(), + filters, }), - }), - }); - }; + }); + }, + [pathname, history, services.data.query.queryString] + ); }; -export const useNavigateFindingsByResource = () => { - const history = useHistory(); +export const useNavigateFindings = () => useNavigate(findingsNavigation.findings_default.path); - return (query: Query['query'] = {}) => { - history.push({ - pathname: findingsNavigation.findings_by_resource.path, - ...(query && { - search: encodeQuery({ - ...getFindingsQuery(query), - filters: [], - }), - }), - }); - }; -}; +export const useNavigateFindingsByResource = () => + useNavigate(findingsNavigation.findings_by_resource.path); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx index 3585e63cf5143..ca96f0010448c 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/aws_credentials_form.tsx @@ -12,6 +12,7 @@ import { EuiLink, EuiSpacer, EuiText, + EuiTitle, } from '@elastic/eui'; import type { NewPackagePolicy } from '@kbn/fleet-plugin/public'; import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common'; @@ -20,6 +21,29 @@ import { i18n } from '@kbn/i18n'; import { RadioGroup } from './csp_boxed_radio_group'; import { getPosturePolicy, NewPackagePolicyPostureInput } from './utils'; +const AWSSetupInfoContent = () => ( + <> + + +

      + +

      +
      + + + + + +); + const DocsLink = ( -
      ); @@ -61,7 +84,6 @@ const DirectAccessKeysDescription = ( defaultMessage="Access keys are long-term credentials for an IAM user or the AWS account root user." /> -
); @@ -75,7 +97,6 @@ const TemporaryKeysDescription = ( found using GetSessionToken." /> - ); @@ -88,7 +109,6 @@ const SharedCredentialsDescription = ( to define multiple access keys in the same configuration file." /> - ); @@ -170,6 +190,7 @@ const options: AwsOptions = { }; export type AwsCredentialsType = keyof typeof options; +export const DEFAULT_AWS_VARS_GROUP: AwsCredentialsType = 'assume_role'; const AWS_CREDENTIALS_OPTIONS = Object.keys(options).map((value) => ({ id: value as AwsCredentialsType, label: options[value as keyof typeof options].label, @@ -209,6 +230,7 @@ export const AwsCredentialsForm = ({ input, newPolicy, updatePolicy }: Props) => return ( <> + /> {group.info} + {DocsLink} onChange(id as AwsCredentialsType)} /> ); + const AwsInputVarFields = ({ fields, onChange, diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts index ecc435aa5961d..ceefc2a2b6333 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts @@ -5,153 +5,19 @@ * 2.0. */ import type { NewPackagePolicy } from '@kbn/fleet-plugin/public'; -import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { createNewPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; -import { BenchmarkId } from '../../../common/types'; -import { CLOUDBEAT_EKS, CLOUDBEAT_VANILLA } from '../../../common/constants'; +import { + CLOUDBEAT_GCP, + CLOUDBEAT_AZURE, + CLOUDBEAT_EKS, + CLOUDBEAT_VANILLA, + CLOUDBEAT_AWS, +} from '../../../common/constants'; import type { PostureInput } from '../../../common/types'; -export const getCspNewPolicyMock = (type: BenchmarkId = 'cis_k8s'): NewPackagePolicy => ({ - name: 'some-cloud_security_posture-policy', - description: '', - namespace: 'default', - policy_id: '', - enabled: true, - inputs: [ - { - type: CLOUDBEAT_VANILLA, - policy_template: 'kspm', - enabled: type === 'cis_k8s', - streams: [ - { - enabled: type === 'cis_k8s', - data_stream: { - type: 'logs', - dataset: 'cloud_security_posture.findings', - }, - }, - ], - }, - { - type: CLOUDBEAT_EKS, - policy_template: 'kspm', - enabled: type === 'cis_eks', - streams: [ - { - enabled: type === 'cis_eks', - data_stream: { - type: 'logs', - dataset: 'cloud_security_posture.findings', - }, - vars: { - access_key_id: { - type: 'text', - }, - secret_access_key: { - type: 'text', - }, - session_token: { - type: 'text', - }, - shared_credential_file: { - type: 'text', - }, - credential_profile_name: { - type: 'text', - }, - role_arn: { - type: 'text', - }, - }, - }, - ], - }, - ], - package: { - name: 'cloud_security_posture', - title: 'Kubernetes Security Posture Management', - version: '0.0.21', - }, - vars: { - runtimeCfg: { - type: 'yaml', - }, - }, -}); - -export const getCspPolicyMock = (type: BenchmarkId = 'cis_k8s'): PackagePolicy => ({ - ...getCspNewPolicyMock(type), - id: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1', - version: 'abcd', - revision: 1, - updated_at: '2020-06-25T16:03:38.159292', - updated_by: 'kibana', - created_at: '2020-06-25T16:03:38.159292', - created_by: 'kibana', - inputs: [ - { - policy_template: 'kspm', - streams: [ - { - compiled_stream: { - data_yaml: { - activated_rules: { - cis_k8s: [], - cis_eks: ['cis_3_1_4'], - }, - }, - name: 'Findings', - processors: [{ add_cluster_id: null }], - }, - data_stream: { - type: 'logs', - dataset: 'cloud_security_posture.findings', - }, - id: 'cloudbeat/vanilla-cloud_security_posture.findings-de97ed6f-5024-46af-a4f9-9acd7bd012d8', - enabled: true, - }, - ], - type: CLOUDBEAT_VANILLA, - enabled: type === 'cis_k8s', - }, - { - policy_template: 'kspm', - streams: [ - { - data_stream: { - type: 'logs', - dataset: 'cloud_security_posture.findings', - }, - vars: { - access_key_id: { - type: 'text', - }, - session_token: { - type: 'text', - }, - secret_access_key: { - type: 'text', - }, - }, - id: 'cloudbeat/eks-cloud_security_posture.findings-de97ed6f-5024-46af-a4f9-9acd7bd012d8', - enabled: false, - }, - ], - type: CLOUDBEAT_EKS, - enabled: type === 'cis_eks', - }, - ], - vars: { - dataYaml: { - type: 'yaml', - value: 'data_yaml:\n activated_rules:\n cis_k8s: []\n cis_eks:\n - cis_3_1_4\n ', - }, - }, -}); - -export const getMockPolicyAWS = () => getPolicyMock('cloudbeat/cis_aws'); -export const getMockPolicyK8s = () => getPolicyMock('cloudbeat/cis_k8s'); -export const getMockPolicyEKS = () => getPolicyMock('cloudbeat/cis_eks'); +export const getMockPolicyAWS = () => getPolicyMock(CLOUDBEAT_AWS); +export const getMockPolicyK8s = () => getPolicyMock(CLOUDBEAT_VANILLA); +export const getMockPolicyEKS = () => getPolicyMock(CLOUDBEAT_EKS); const getPolicyMock = (type: PostureInput): NewPackagePolicy => { const mockPackagePolicy = createNewPackagePolicyMock(); @@ -178,42 +44,38 @@ const getPolicyMock = (type: PostureInput): NewPackagePolicy => { }, vars: { posture: { - value: type === 'cloudbeat/cis_k8s' || type === 'cloudbeat/cis_eks' ? 'kspm' : 'cspm', + value: type === CLOUDBEAT_VANILLA || type === CLOUDBEAT_EKS ? 'kspm' : 'cspm', type: 'text', }, deployment: { value: type, type: 'text' }, }, inputs: [ { - type: 'cloudbeat/cis_k8s', + type: CLOUDBEAT_VANILLA, policy_template: 'kspm', - enabled: type === 'cloudbeat/cis_k8s', - streams: [{ enabled: type === 'cloudbeat/cis_k8s', data_stream: dataStream }], + enabled: type === CLOUDBEAT_VANILLA, + streams: [{ enabled: type === CLOUDBEAT_VANILLA, data_stream: dataStream }], }, { - type: 'cloudbeat/cis_eks', + type: CLOUDBEAT_EKS, policy_template: 'kspm', - enabled: type === 'cloudbeat/cis_eks', - streams: [ - { enabled: type === 'cloudbeat/cis_eks', data_stream: dataStream, vars: awsVarsMock }, - ], + enabled: type === CLOUDBEAT_EKS, + streams: [{ enabled: type === CLOUDBEAT_EKS, data_stream: dataStream, vars: awsVarsMock }], }, { - type: 'cloudbeat/cis_aws', + type: CLOUDBEAT_AWS, policy_template: 'cspm', - enabled: type === 'cloudbeat/cis_aws', - streams: [ - { enabled: type === 'cloudbeat/cis_aws', data_stream: dataStream, vars: awsVarsMock }, - ], + enabled: type === CLOUDBEAT_AWS, + streams: [{ enabled: type === CLOUDBEAT_AWS, data_stream: dataStream, vars: awsVarsMock }], }, { - type: 'cloudbeat/cis_gcp', + type: CLOUDBEAT_GCP, policy_template: 'cspm', enabled: false, streams: [{ enabled: false, data_stream: dataStream }], }, { - type: 'cloudbeat/cis_azure', + type: CLOUDBEAT_AZURE, policy_template: 'cspm', enabled: false, streams: [{ enabled: false, data_stream: dataStream }], diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx deleted file mode 100644 index b4d3828dd97b8..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_create.tsx +++ /dev/null @@ -1,20 +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, { memo } from 'react'; -import type { PackagePolicyCreateExtensionComponentProps } from '@kbn/fleet-plugin/public'; -import { CspPolicyTemplateForm } from './policy_template_form'; - -export const CspCreatePolicyExtension = memo( - ({ newPolicy, onChange }) => ( - - ) -); - -CspCreatePolicyExtension.displayName = 'CspCreatePolicyExtension'; - -// eslint-disable-next-line import/no-default-export -export { CspCreatePolicyExtension as default }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx deleted file mode 100644 index b2be1b0c0d7cc..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_extension_edit.tsx +++ /dev/null @@ -1,20 +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, { memo } from 'react'; -import type { PackagePolicyEditExtensionComponentProps } from '@kbn/fleet-plugin/public'; -import { CspPolicyTemplateForm } from './policy_template_form'; - -export const CspEditPolicyExtension = memo( - ({ newPolicy, onChange }) => ( - - ) -); - -CspEditPolicyExtension.displayName = 'CspEditPolicyExtension'; - -// eslint-disable-next-line import/no-default-export -export { CspEditPolicyExtension as default }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx index 0d4ab59bd31b9..37d4a13e8a532 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx @@ -9,7 +9,7 @@ import { render } from '@testing-library/react'; import { CspPolicyTemplateForm } from './policy_template_form'; import { TestProvider } from '../../test/test_provider'; import { getMockPolicyAWS, getMockPolicyEKS, getMockPolicyK8s } from './mocks'; -import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; +import type { NewPackagePolicy, PackageInfo, PackagePolicy } from '@kbn/fleet-plugin/common'; import userEvent from '@testing-library/user-event'; import { getPosturePolicy } from './utils'; import { CLOUDBEAT_AWS, CLOUDBEAT_EKS } from '../../../common/constants'; @@ -25,7 +25,23 @@ describe('', () => { newPolicy: NewPackagePolicy; }) => ( - + {edit && ( + + )} + {!edit && ( + + )} ); @@ -33,6 +49,38 @@ describe('', () => { onChange.mockClear(); }); + it('renders and updates name field', () => { + const policy = getMockPolicyK8s(); + const { getByLabelText } = render(); + const name = getByLabelText('Name'); + expect(name).toBeInTheDocument(); + + userEvent.type(name, '1'); + + // Listen to the 2nd triggered by the test. + // The 1st is done on mount to ensure initial state is valid. + expect(onChange).toHaveBeenNthCalledWith(2, { + isValid: true, + updatedPolicy: { ...policy, name: `${policy.name}1` }, + }); + }); + + it('renders and updates description field', () => { + const policy = getMockPolicyK8s(); + const { getByLabelText } = render(); + const description = getByLabelText('Description'); + expect(description).toBeInTheDocument(); + + userEvent.type(description, '1'); + + // Listen to the 2nd triggered by the test. + // The 1st is done on mount to ensure initial state is valid. + expect(onChange).toHaveBeenNthCalledWith(2, { + isValid: true, + updatedPolicy: { ...policy, description: `${policy.description}1` }, + }); + }); + it('renders KSPM input selector', () => { const { getByLabelText } = render(); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx index eee8f86ef6b48..da77b3965ed19 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx @@ -5,87 +5,157 @@ * 2.0. */ import React, { memo, useEffect } from 'react'; -import { EuiSpacer } from '@elastic/eui'; -import type { - NewPackagePolicy, - PackagePolicyCreateExtensionComponentProps, -} from '@kbn/fleet-plugin/public'; -import type { PostureInput } from '../../../common/types'; +import { EuiFieldText, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui'; +import type { NewPackagePolicy } from '@kbn/fleet-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { PackagePolicyReplaceDefineStepExtensionComponentProps } from '@kbn/fleet-plugin/public/types'; +import { useParams } from 'react-router-dom'; +import type { PostureInput, PosturePolicyTemplate } from '../../../common/types'; import { CLOUDBEAT_AWS, CLOUDBEAT_VANILLA } from '../../../common/constants'; +import { getPosturePolicy, getEnabledPostureInput, getPostureInputHiddenVars } from './utils'; import { - getPosturePolicy, - INPUTS_WITH_AWS_VARS, - getEnabledPostureInput, - type NewPackagePolicyPostureInput, -} from './utils'; -import { AwsCredentialsForm, type AwsCredentialsType } from './aws_credentials_form'; -import { PolicyInputSelector } from './policy_template_input_selector'; + PolicyTemplateInfo, + PolicyTemplateInputSelector, + PolicyTemplateSelector, + PolicyTemplateVarsForm, +} from './policy_template_selectors'; const DEFAULT_INPUT_TYPE = { kspm: CLOUDBEAT_VANILLA, cspm: CLOUDBEAT_AWS, } as const; -const DEFAULT_AWS_VARS_GROUP: AwsCredentialsType = 'assume_role'; +const EditScreenStepTitle = () => ( + <> + +

+ +

+
+ + +); -interface Props extends PackagePolicyCreateExtensionComponentProps { - edit?: boolean; +interface IntegrationInfoFieldsProps { + fields: Array<{ id: string; value: string; label: React.ReactNode; error: string[] | null }>; + onChange(field: string, value: string): void; } -interface PolicyVarsFormProps { - newPolicy: NewPackagePolicy; - input: NewPackagePolicyPostureInput; - updatePolicy(updatedPolicy: NewPackagePolicy): void; -} +const IntegrationSettings = ({ onChange, fields }: IntegrationInfoFieldsProps) => ( +
+ {fields.map(({ value, id, label, error }) => ( + + onChange(id, event.target.value)} + /> + + ))} +
+); -const PolicyVarsForm = ({ input, ...props }: PolicyVarsFormProps) => { - switch (input.type) { - case 'cloudbeat/cis_aws': - case 'cloudbeat/cis_eks': - return ; - default: - return null; - } -}; - -export const CspPolicyTemplateForm = memo(({ newPolicy, onChange, edit }) => { - const input = getEnabledPostureInput(newPolicy); - - const updatePolicy = (updatedPolicy: NewPackagePolicy) => - onChange({ - isValid: true, - updatedPolicy, - }); - - /** - * - Updates policy inputs by user selection - * - Updates hidden policy vars - */ - const setEnabledPolicyInput = (inputType: PostureInput) => - updatePolicy( - getPosturePolicy( - newPolicy, - inputType, - INPUTS_WITH_AWS_VARS.includes(inputType) - ? { 'aws.credentials.type': { value: DEFAULT_AWS_VARS_GROUP } } - : undefined - ) +export const CspPolicyTemplateForm = memo( + ({ newPolicy, onChange, validationResults, isEditPage }) => { + const { integration } = useParams<{ integration: PosturePolicyTemplate }>(); + const input = getEnabledPostureInput(newPolicy); + + const updatePolicy = (updatedPolicy: NewPackagePolicy) => + onChange({ isValid: true, updatedPolicy }); + + /** + * - Updates policy inputs by user selection + * - Updates hidden policy vars + */ + const setEnabledPolicyInput = (inputType: PostureInput) => { + const inputVars = getPostureInputHiddenVars(inputType); + const policy = getPosturePolicy(newPolicy, inputType, inputVars); + updatePolicy(policy); + }; + + const integrationFields = [ + { + id: 'name', + value: newPolicy.name, + error: validationResults?.name || null, + label: ( + + ), + }, + { + id: 'description', + value: newPolicy.description || '', + error: validationResults?.description || null, + label: ( + + ), + }, + ]; + + useEffect(() => { + if (isEditPage) return; + + // Pick default input type for policy template. + // Only 1 enabled input is supported when all inputs are initially enabled. + setEnabledPolicyInput(DEFAULT_INPUT_TYPE[input.policy_template]); + + // Required for mount only to ensure a single input type is selected + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEditPage]); + + return ( +
+ {isEditPage && } + + {/* Defines the enabled policy template */} + {!integration && ( + <> + setEnabledPolicyInput(DEFAULT_INPUT_TYPE[template])} + disabled={isEditPage} + /> + + + )} + + {/* Shows info on the active policy template */} + + + + {/* Defines the single enabled input of the active policy template */} + + + + {/* Defines the name/description */} + updatePolicy({ ...newPolicy, [field]: value })} + /> + {/* Defines the vars of the enabled input of the active policy template */} + + +
); + } +); + +CspPolicyTemplateForm.displayName = 'CspPolicyTemplateForm'; - useEffect(() => { - // Pick default input type for policy template. - // Only 1 enabled input is supported when all inputs are initially enabled. - if (!edit) setEnabledPolicyInput(DEFAULT_INPUT_TYPE[input.policy_template]); - - // Required for mount only to ensure a single input type is selected - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [edit]); - - return ( -
- - - -
- ); -}); +// eslint-disable-next-line import/no-default-export +export { CspPolicyTemplateForm as default }; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx deleted file mode 100644 index 34d00a95d6899..0000000000000 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_input_selector.tsx +++ /dev/null @@ -1,104 +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 { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import type { PostureInput, PosturePolicyTemplate } from '../../../common/types'; -import { getPolicyTemplateInputOptions, type NewPackagePolicyPostureInput } from './utils'; -import { RadioGroup } from './csp_boxed_radio_group'; - -interface Props { - disabled: boolean; - input: NewPackagePolicyPostureInput; - setInput: (inputType: PostureInput) => void; -} - -export const PolicyInputSelector = ({ input, disabled, setInput }: Props) => { - const baseOptions = getPolicyTemplateInputOptions(input.policy_template); - const options = baseOptions.map((option) => ({ - ...option, - disabled: option.disabled || disabled, - label: option.label, - icon: option.icon, - })); - - return ( -
- - setInput(inputType as PostureInput)} - size="m" - /> - -
- ); -}; - -const PolicyInputInfo = ({ type }: { type: PostureInput }) => { - switch (type) { - case 'cloudbeat/cis_aws': - case 'cloudbeat/cis_eks': - return ; - default: - return null; - } -}; - -const AWSSetupInfoContent = () => ( - <> - - -

- -

-
- - - - - -); - -const ConfigureIntegrationInfo = ({ type }: { type: PosturePolicyTemplate }) => ( - <> - -

- -

-
- - - {type === 'kspm' && ( - - )} - {type === 'cspm' && ( - - )} - - - -); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx new file mode 100644 index 0000000000000..083c700c6f6e1 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_selectors.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import type { NewPackagePolicy } from '@kbn/fleet-plugin/common'; +import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants'; +import type { PostureInput, PosturePolicyTemplate } from '../../../common/types'; +import { getPolicyTemplateInputOptions, type NewPackagePolicyPostureInput } from './utils'; +import { RadioGroup } from './csp_boxed_radio_group'; +import { AwsCredentialsForm } from './aws_credentials_form'; + +interface PolicyTemplateSelectorProps { + selectedTemplate: PosturePolicyTemplate; + policy: NewPackagePolicy; + setPolicyTemplate(template: PosturePolicyTemplate): void; + disabled: boolean; +} + +export const PolicyTemplateSelector = ({ + policy, + selectedTemplate, + setPolicyTemplate, + disabled, +}: PolicyTemplateSelectorProps) => { + const policyTemplates = new Set(policy.inputs.map((input) => input.policy_template!)); + + return ( +
+ + + + + ({ id: v, label: v.toUpperCase() }))} + idSelected={selectedTemplate} + onChange={(id) => setPolicyTemplate(id as PosturePolicyTemplate)} + disabled={disabled} + /> +
+ ); +}; + +interface PolicyTemplateVarsFormProps { + newPolicy: NewPackagePolicy; + input: NewPackagePolicyPostureInput; + updatePolicy(updatedPolicy: NewPackagePolicy): void; +} + +export const PolicyTemplateVarsForm = ({ input, ...props }: PolicyTemplateVarsFormProps) => { + switch (input.type) { + case 'cloudbeat/cis_aws': + case 'cloudbeat/cis_eks': + return ; + default: + return null; + } +}; + +interface PolicyTemplateInfoProps { + postureType: PosturePolicyTemplate; +} + +export const PolicyTemplateInfo = ({ postureType }: PolicyTemplateInfoProps) => ( + + {postureType === KSPM_POLICY_TEMPLATE && ( + + )} + {postureType === CSPM_POLICY_TEMPLATE && ( + + )} + +); + +interface Props { + disabled: boolean; + input: NewPackagePolicyPostureInput; + setInput: (inputType: PostureInput) => void; +} + +export const PolicyTemplateInputSelector = ({ input, disabled, setInput }: Props) => { + const baseOptions = getPolicyTemplateInputOptions(input.policy_template); + const options = baseOptions.map((option) => ({ + ...option, + disabled: option.disabled || disabled, + label: option.label, + icon: option.icon, + })); + + return ( + setInput(inputType as PostureInput)} + size="m" + /> + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts new file mode 100644 index 0000000000000..aaed63a91da0a --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.test.ts @@ -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 { getPostureInputHiddenVars, getPosturePolicy } from './utils'; +import { getMockPolicyAWS, getMockPolicyK8s, getMockPolicyEKS } from './mocks'; + +describe('getPosturePolicy', () => { + for (const [name, getPolicy, expectedVars] of [ + ['cloudbeat/cis_aws', getMockPolicyAWS, { 'aws.credentials.type': { value: 'assume_role' } }], + ['cloudbeat/cis_eks', getMockPolicyEKS, { 'aws.credentials.type': { value: 'assume_role' } }], + ['cloudbeat/cis_k8s', getMockPolicyK8s, null], + ] as const) { + it(`updates package policy with hidden vars for ${name}`, () => { + const inputVars = getPostureInputHiddenVars(name); + const policy = getPosturePolicy(getPolicy(), name, inputVars); + + const enabledInputs = policy.inputs.filter( + (i) => i.type === name && i.enabled && i.streams.some((s) => s.enabled) + ); + + expect(enabledInputs.length).toBe(1); + if (expectedVars) expect(enabledInputs[0].streams[0].vars).toMatchObject({ ...expectedVars }); + else expect(enabledInputs[0].streams[0].vars).toBe(undefined); + }); + } + + it('updates package policy required vars (posture/deployment)', () => { + const mockCisAws = getMockPolicyAWS(); + expect(mockCisAws.vars?.posture.value).toBe('cspm'); + mockCisAws.vars!.extra = { value: 'value' }; + + const policy = getPosturePolicy(mockCisAws, 'cloudbeat/cis_k8s'); + expect(policy.vars?.posture.value).toBe('kspm'); + expect(policy.vars?.deployment.value).toBe('cloudbeat/cis_k8s'); + + // Does not change extra vars + expect(policy.vars?.extra.value).toBe('value'); + }); + + it('updates package policy with a single enabled input', () => { + const mockCisAws = getMockPolicyAWS(); + expect(mockCisAws.inputs.filter((i) => i.enabled).length).toBe(1); + expect(mockCisAws.inputs.filter((i) => i.enabled)[0].type).toBe('cloudbeat/cis_aws'); + + // enable all inputs of a policy + mockCisAws.inputs = mockCisAws.inputs.map((i) => ({ + ...i, + enabled: true, + streams: i.streams.map((s) => ({ ...s, enabled: true })), + })); + + // change input + const policy = getPosturePolicy(mockCisAws, 'cloudbeat/cis_k8s'); + const enabledInputs = policy.inputs.filter( + (i) => i.enabled && i.streams.some((s) => s.enabled) + ); + + expect(enabledInputs.length).toBe(1); + expect(enabledInputs.map((v) => v.type)[0]).toBe('cloudbeat/cis_k8s'); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts index afbd7ffac9bf8..2d8f670b5a0fd 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts @@ -19,12 +19,11 @@ import { SUPPORTED_POLICY_TEMPLATES, SUPPORTED_CLOUDBEAT_INPUTS, } from '../../../common/constants'; +import { DEFAULT_AWS_VARS_GROUP } from './aws_credentials_form'; import type { PostureInput, PosturePolicyTemplate } from '../../../common/types'; import { assert } from '../../../common/utils/helpers'; import { cloudPostureIntegrations } from '../../common/constants'; -export const INPUTS_WITH_AWS_VARS = [CLOUDBEAT_EKS, CLOUDBEAT_AWS]; - type PosturePolicyInput = | { type: typeof CLOUDBEAT_AZURE; policy_template: 'cspm' } | { type: typeof CLOUDBEAT_GCP; policy_template: 'cspm' } @@ -60,7 +59,9 @@ const getPostureInput = ( // Merge new vars with existing vars ...(isInputEnabled && stream.vars && - inputVars && { vars: merge({}, stream.vars, inputVars) }), + inputVars && { + vars: merge({}, stream.vars, inputVars), + }), })), }; }; @@ -83,6 +84,19 @@ export const getPosturePolicy = ( }), }); +/** + * Input vars that are hidden from the user + */ +export const getPostureInputHiddenVars = (inputType: PostureInput) => { + switch (inputType) { + case 'cloudbeat/cis_aws': + case 'cloudbeat/cis_eks': + return { 'aws.credentials.type': { value: DEFAULT_AWS_VARS_GROUP } }; + default: + return undefined; + } +}; + export const getPolicyTemplateInputOptions = (policyTemplate: PosturePolicyTemplate) => cloudPostureIntegrations[policyTemplate].options.map((o) => ({ tooltip: o.tooltip, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx index f082171b22da1..968db8feb53b2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx @@ -11,65 +11,11 @@ import * as TEST_SUBJECTS from '../test_subjects'; import { FindingsTable } from './latest_findings_table'; import type { PropsOf } from '@elastic/eui'; import Chance from 'chance'; -import type { EcsEvent } from '@kbn/ecs'; import { TestProvider } from '../../../test/test_provider'; -import { CspFinding } from '../../../../common/schemas/csp_finding'; +import { getFindingsFixture } from '../../../test/fixtures/findings_fixture'; const chance = new Chance(); -const getFakeFindings = (name: string): CspFinding & { id: string } => ({ - cluster_id: chance.guid(), - id: chance.word(), - result: { - expected: { - source: {}, - }, - evaluation: chance.weighted(['passed', 'failed'], [0.5, 0.5]), - evidence: { - filemode: chance.word(), - }, - }, - rule: { - audit: chance.paragraph(), - benchmark: { - rule_number: '1.1.1', - name: 'CIS Kubernetes', - version: '1.6.0', - id: 'cis_k8s', - }, - default_value: chance.sentence(), - description: chance.paragraph(), - id: chance.guid(), - impact: chance.word(), - name, - profile_applicability: chance.sentence(), - rationale: chance.paragraph(), - references: chance.paragraph(), - rego_rule_id: 'cis_X_X_X', - remediation: chance.word(), - section: chance.sentence(), - tags: [], - version: '1.0', - }, - agent: { - id: chance.string(), - name: chance.string(), - type: chance.string(), - version: chance.string(), - }, - resource: { - name: chance.string(), - type: chance.string(), - raw: {} as any, - sub_type: chance.string(), - id: chance.string(), - }, - host: {} as any, - ecs: {} as any, - event: {} as EcsEvent, - '@timestamp': new Date().toISOString(), -}); - type TableProps = PropsOf; describe('', () => { @@ -96,7 +42,7 @@ describe('', () => { it('renders the table with provided items', () => { const names = chance.unique(chance.sentence, 10); - const data = names.map(getFakeFindings); + const data = names.map(getFindingsFixture); const props: TableProps = { loading: false, @@ -120,7 +66,7 @@ describe('', () => { it('adds filter with a cell button click', () => { const names = chance.unique(chance.sentence, 10); - const data = names.map(getFakeFindings); + const data = names.map(getFindingsFixture); const filterProps = { onAddFilter: jest.fn() }; const props: TableProps = { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx index 630edd6cb8dcf..fb7ce817246a9 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_table.tsx @@ -57,13 +57,28 @@ const FindingsByResourceTableComponent = ({ 'data-test-subj': TEST_SUBJECTS.getFindingsByResourceTableRowTestId(getResourceId(row)), }); + const getNonSortableColumn = (column: EuiTableFieldDataColumnType) => ({ + ...column, + sortable: false, + }); + const columns = useMemo( () => [ - findingsByResourceColumns.resource_id, - createColumnWithFilters(findingsByResourceColumns['resource.sub_type'], { onAddFilter }), - createColumnWithFilters(findingsByResourceColumns['resource.name'], { onAddFilter }), - createColumnWithFilters(findingsByResourceColumns['rule.benchmark.name'], { onAddFilter }), - createColumnWithFilters(findingsByResourceColumns.cluster_id, { onAddFilter }), + getNonSortableColumn(findingsByResourceColumns.resource_id), + createColumnWithFilters( + getNonSortableColumn(findingsByResourceColumns['resource.sub_type']), + { onAddFilter } + ), + createColumnWithFilters(getNonSortableColumn(findingsByResourceColumns['resource.name']), { + onAddFilter, + }), + createColumnWithFilters( + getNonSortableColumn(findingsByResourceColumns['rule.benchmark.name']), + { onAddFilter } + ), + createColumnWithFilters(getNonSortableColumn(findingsByResourceColumns.cluster_id), { + onAddFilter, + }), findingsByResourceColumns.compliance_score, ], [onAddFilter] diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx index 05cc930a314f8..66a561e676098 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx @@ -135,7 +135,6 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { }), }); }; - return (
{ title={i18n.translate( 'xpack.csp.findings.resourceFindings.resourceFindingsPageTitle', { - defaultMessage: '{resourceName} - Findings', - values: { resourceName: resourceFindings.data?.resourceName }, + defaultMessage: '{resourceName} {hyphen} Findings', + values: { + resourceName: resourceFindings.data?.resourceName, + hyphen: resourceFindings.data?.resourceName ? '-' : '', + }, } )} /> @@ -167,9 +169,9 @@ export const ResourceFindings = ({ dataView }: FindingsBaseProps) => { ) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.test.tsx new file mode 100644 index 0000000000000..101eb9d6ae67c --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.test.tsx @@ -0,0 +1,77 @@ +/* + * 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, screen, within } from '@testing-library/react'; +import * as TEST_SUBJECTS from '../../test_subjects'; +import { ResourceFindingsTable, ResourceFindingsTableProps } from './resource_findings_table'; +import { TestProvider } from '../../../../test/test_provider'; + +import { capitalize } from 'lodash'; +import moment from 'moment'; +import { getFindingsFixture } from '../../../../test/fixtures/findings_fixture'; + +describe('', () => { + it('should render no findings empty state when status success and data has a length of zero ', async () => { + const resourceFindingsProps: ResourceFindingsTableProps = { + loading: false, + items: [], + pagination: { pageIndex: 0, pageSize: 10, totalItemCount: 0 }, + sorting: { + sort: { field: '@timestamp', direction: 'desc' }, + }, + setTableOptions: jest.fn(), + onAddFilter: jest.fn(), + }; + + render( + + + + ); + + expect( + screen.getByTestId(TEST_SUBJECTS.RESOURCES_FINDINGS_TABLE_EMPTY_STATE) + ).toBeInTheDocument(); + }); + + it('should render resource finding table content when data has a non zero length', () => { + const data = Array.from({ length: 10 }, getFindingsFixture); + + const props: ResourceFindingsTableProps = { + loading: false, + items: data, + pagination: { pageIndex: 0, pageSize: 10, totalItemCount: 0 }, + sorting: { + sort: { field: 'cluster_id', direction: 'desc' }, + }, + setTableOptions: jest.fn(), + onAddFilter: jest.fn(), + }; + + render( + + + + ); + + data.forEach((item, i) => { + const row = screen.getByTestId( + TEST_SUBJECTS.getResourceFindingsTableRowTestId(item.resource.id) + ); + const { evaluation } = item.result; + const evaluationStatusText = capitalize( + item.result.evaluation.slice(0, evaluation.length - 2) + ); + + expect(row).toBeInTheDocument(); + expect(within(row).queryByText(item.rule.name)).toBeInTheDocument(); + expect(within(row).queryByText(evaluationStatusText)).toBeInTheDocument(); + expect(within(row).queryByText(moment(item['@timestamp']).fromNow())).toBeInTheDocument(); + expect(within(row).queryByText(item.rule.section)).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx index 41f18af723b8e..50a9c19c6b6ab 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_table.tsx @@ -25,8 +25,9 @@ import { } from '../../layout/findings_layout'; import { FindingsRuleFlyout } from '../../findings_flyout/findings_flyout'; import { getSelectedRowStyle } from '../../utils/utils'; +import * as TEST_SUBJECTS from '../../test_subjects'; -interface Props { +export interface ResourceFindingsTableProps { items: CspFinding[]; loading: boolean; pagination: Pagination; @@ -42,12 +43,13 @@ const ResourceFindingsTableComponent = ({ sorting, setTableOptions, onAddFilter, -}: Props) => { +}: ResourceFindingsTableProps) => { const { euiTheme } = useEuiTheme(); const [selectedFinding, setSelectedFinding] = useState(); const getRowProps = (row: CspFinding) => ({ style: getSelectedRowStyle(euiTheme, row, selectedFinding), + 'data-test-subj': TEST_SUBJECTS.getResourceFindingsTableRowTestId(row.resource.id), }); const columns: [ @@ -69,6 +71,7 @@ const ResourceFindingsTableComponent = ({ return ( + estypes.AggregationsMultiBucketAggregateBase< + estypes.AggregationsStringRareTermsBucketKeys | undefined + > >; const getResourceFindingsQuery = ({ @@ -92,19 +94,18 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { keepPreviousData: true, select: ({ rawResponse: { hits, aggregations } }: ResourceFindingsResponse) => { if (!aggregations) throw new Error('expected aggregations to exists'); - - assertNonEmptyArray(aggregations.count.buckets); - assertNonEmptyArray(aggregations.clusterId.buckets); - assertNonEmptyArray(aggregations.resourceSubType.buckets); - assertNonEmptyArray(aggregations.resourceName.buckets); + assertNonBucketsArray(aggregations.count?.buckets); + assertNonBucketsArray(aggregations.clusterId?.buckets); + assertNonBucketsArray(aggregations.resourceSubType?.buckets); + assertNonBucketsArray(aggregations.resourceName?.buckets); return { page: hits.hits.map((hit) => hit._source!), total: number.is(hits.total) ? hits.total : 0, - count: getAggregationCount(aggregations.count.buckets), - clusterId: getFirstBucketKey(aggregations.clusterId.buckets), - resourceSubType: getFirstBucketKey(aggregations.resourceSubType.buckets), - resourceName: getFirstBucketKey(aggregations.resourceName.buckets), + count: getAggregationCount(aggregations.count?.buckets), + clusterId: getFirstBucketKey(aggregations.clusterId?.buckets), + resourceSubType: getFirstBucketKey(aggregations.resourceSubType?.buckets), + resourceName: getFirstBucketKey(aggregations.resourceName?.buckets), }; }, onError: (err: Error) => showErrorToast(toasts, err), @@ -112,11 +113,12 @@ export const useResourceFindings = (options: UseResourceFindingsOptions) => { ); }; -function assertNonEmptyArray(arr: unknown): asserts arr is T[] { - if (!Array.isArray(arr) || arr.length === 0) { - throw new Error('expected a non empty array'); +function assertNonBucketsArray(arr: unknown): asserts arr is T[] { + if (!Array.isArray(arr)) { + throw new Error('expected buckets to be an array'); } } -const getFirstBucketKey = (buckets: estypes.AggregationsStringRareTermsBucketKeys[]) => - buckets[0].key; +const getFirstBucketKey = ( + buckets: Array +): string | undefined => buckets[0]?.key; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/test_subjects.ts index 28c15e4913800..153811865c5c3 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/test_subjects.ts @@ -21,5 +21,10 @@ export const getFindingsTableCellTestId = (columnId: string, rowId: string) => export const FINDINGS_TABLE_CELL_ADD_FILTER = 'findings_table_cell_add_filter'; export const FINDINGS_TABLE_CELL_ADD_NEGATED_FILTER = 'findings_table_cell_add_negated_filter'; +export const RESOURCES_FINDINGS_TABLE_EMPTY_STATE = 'resource_findings_table_empty_state'; +export const RESOURCES_FINDINGS_TABLE = 'resource_findings_table'; +export const getResourceFindingsTableRowTestId = (id: string) => + `resource_findings_table_row_${id}`; + export const DASHBOARD_TABLE_HEADER_SCORE_TEST_ID = 'csp:dashboard-sections-table-header-score'; export const DASHBOARD_TABLE_COLUMN_SCORE_TEST_ID = 'csp:dashboard-sections-table-column-score'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts index 984f0d2dfcaf8..32cb914b446e2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/utils/utils.ts @@ -120,9 +120,11 @@ export const getFindingsCountAggQuery = () => ({ count: { terms: { field: 'result.evaluation' } }, }); -export const getAggregationCount = (buckets: estypes.AggregationsStringRareTermsBucketKeys[]) => { - const passed = buckets.find((bucket) => bucket.key === 'passed'); - const failed = buckets.find((bucket) => bucket.key === 'failed'); +export const getAggregationCount = ( + buckets: Array +) => { + const passed = buckets.find((bucket) => bucket?.key === 'passed'); + const failed = buckets.find((bucket) => bucket?.key === 'failed'); return { passed: passed?.doc_count || 0, diff --git a/x-pack/plugins/cloud_security_posture/public/plugin.tsx b/x-pack/plugins/cloud_security_posture/public/plugin.tsx index 3644bd19d49b7..9114dcd9e2f4d 100755 --- a/x-pack/plugins/cloud_security_posture/public/plugin.tsx +++ b/x-pack/plugins/cloud_security_posture/public/plugin.tsx @@ -19,10 +19,10 @@ import type { import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../common/constants'; import { SetupContext } from './application/setup_context'; -const LazyCspEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit')); -const LazyCspCreatePolicy = lazy( - () => import('./components/fleet_extensions/policy_extension_create') +const LazyCspPolicyTemplateForm = lazy( + () => import('./components/fleet_extensions/policy_template_form') ); + const LazyCspCustomAssets = lazy( () => import('./components/fleet_extensions/custom_assets_extension') ); @@ -57,14 +57,8 @@ export class CspPlugin public start(core: CoreStart, plugins: CspClientPluginStartDeps): CspClientPluginStart { plugins.fleet.registerExtension({ package: CLOUD_SECURITY_POSTURE_PACKAGE_NAME, - view: 'package-policy-create', - Component: LazyCspCreatePolicy, - }); - - plugins.fleet.registerExtension({ - package: CLOUD_SECURITY_POSTURE_PACKAGE_NAME, - view: 'package-policy-edit', - Component: LazyCspEditPolicy, + view: 'package-policy-replace-define-step', + Component: LazyCspPolicyTemplateForm, }); plugins.fleet.registerExtension({ diff --git a/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts new file mode 100644 index 0000000000000..f2c325787f9f6 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/test/fixtures/findings_fixture.ts @@ -0,0 +1,64 @@ +/* + * 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 { EcsEvent } from '@kbn/ecs'; +import Chance from 'chance'; +import { CspFinding } from '../../../common/schemas/csp_finding'; + +const chance = new Chance(); + +export const getFindingsFixture = (): CspFinding & { id: string } => ({ + cluster_id: chance.guid(), + id: chance.word(), + result: { + expected: { + source: {}, + }, + evaluation: chance.weighted(['passed', 'failed'], [0.5, 0.5]), + evidence: { + filemode: chance.word(), + }, + }, + rule: { + audit: chance.paragraph(), + benchmark: { + name: 'CIS Kubernetes', + version: '1.6.0', + id: 'cis_k8s', + }, + default_value: chance.sentence(), + description: chance.paragraph(), + id: chance.guid(), + impact: chance.word(), + name: chance.string(), + profile_applicability: chance.sentence(), + rationale: chance.paragraph(), + references: chance.paragraph(), + rego_rule_id: 'cis_X_X_X', + remediation: chance.word(), + section: chance.sentence(), + tags: [], + version: '1.0', + }, + agent: { + id: chance.string(), + name: chance.string(), + type: chance.string(), + version: chance.string(), + }, + resource: { + name: chance.string(), + type: chance.string(), + raw: {} as any, + sub_type: chance.string(), + id: chance.string(), + }, + host: {} as any, + ecs: {} as any, + event: {} as EcsEvent, + '@timestamp': new Date().toISOString(), +}); diff --git a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts index 17c08cf3006e9..4a3b85b41a159 100644 --- a/x-pack/plugins/cloud_security_posture/server/plugin.test.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.test.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { coreMock, httpServerMock } from '@kbn/core/server/mocks'; +import { + coreMock, + elasticsearchServiceMock, + httpServerMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; import { createPackagePolicyServiceMock, createArtifactsClientMock, @@ -43,6 +48,7 @@ import { } from '@kbn/core/server'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; +import * as onPackagePolicyPostCreateCallback from './fleet_integration/fleet_integration'; const chance = new Chance(); @@ -147,12 +153,18 @@ describe('Cloud Security Posture Plugin', () => { }); it('should initialize when new package is created', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; fleetMock.packageService.asInternalUser.getInstallation.mockImplementationOnce( async (): Promise => { return; } ); + const onPackagePolicyPostCreateCallbackSpy = jest + .spyOn(onPackagePolicyPostCreateCallback, 'onPackagePolicyPostCreateCallback') + .mockResolvedValue(); + const packageMock = createPackagePolicyMock(); packageMock.package!.name = CLOUD_SECURITY_POSTURE_PACKAGE_NAME; @@ -172,19 +184,30 @@ describe('Cloud Security Posture Plugin', () => { await mockPlugins.fleet.fleetSetupCompleted(); // Assert + expect(onPackagePolicyPostCreateCallbackSpy).not.toHaveBeenCalled(); expect(fleetMock.packageService.asInternalUser.getInstallation).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(0); expect(packagePolicyPostCreateCallbacks.length).toBeGreaterThan(0); for (const cb of packagePolicyPostCreateCallbacks) { - await cb(packageMock, contextMock, httpServerMock.createKibanaRequest()); + await cb( + packageMock, + soClient, + esClient, + contextMock, + httpServerMock.createKibanaRequest() + ); } + expect(onPackagePolicyPostCreateCallbackSpy).toHaveBeenCalled(); expect(spy).toHaveBeenCalledTimes(1); }); it('should not initialize when other package is created', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + fleetMock.packageService.asInternalUser.getInstallation.mockImplementationOnce( async (): Promise => { return; @@ -216,7 +239,13 @@ describe('Cloud Security Posture Plugin', () => { expect(packagePolicyPostCreateCallbacks.length).toBeGreaterThan(0); for (const cb of packagePolicyPostCreateCallbacks) { - await cb(packageMock, contextMock, httpServerMock.createKibanaRequest()); + await cb( + packageMock, + soClient, + esClient, + contextMock, + httpServerMock.createKibanaRequest() + ); } expect(spy).toHaveBeenCalledTimes(0); @@ -266,9 +295,14 @@ describe('Cloud Security Posture Plugin', () => { expect(packagePolicyPostCreateCallbacks.length).toBeGreaterThan(0); + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + for (const cb of packagePolicyPostCreateCallbacks) { const updatedPackagePolicy = await cb( packageMock, + soClient, + esClient, contextMock, httpServerMock.createKibanaRequest() ); @@ -284,6 +318,9 @@ describe('Cloud Security Posture Plugin', () => { ])( 'should uninstall resources when package is removed', async (total, items, expectedNumberOfCallsToUninstallResources) => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + fleetMock.packagePolicyService.list.mockImplementationOnce( async (): Promise> => { return { @@ -320,7 +357,7 @@ describe('Cloud Security Posture Plugin', () => { expect(packagePolicyPostDeleteCallbacks.length).toBeGreaterThan(0); for (const cb of packagePolicyPostDeleteCallbacks) { - await cb(deletedPackagePolicyMock); + await cb(deletedPackagePolicyMock, soClient, esClient); } expect(fleetMock.packagePolicyService.list).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(expectedNumberOfCallsToUninstallResources); diff --git a/x-pack/plugins/cloud_security_posture/server/plugin.ts b/x-pack/plugins/cloud_security_posture/server/plugin.ts index 027f92b293d78..7ab5923bcf71a 100755 --- a/x-pack/plugins/cloud_security_posture/server/plugin.ts +++ b/x-pack/plugins/cloud_security_posture/server/plugin.ts @@ -6,13 +6,12 @@ */ import type { - KibanaRequest, - RequestHandlerContext, PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger, + SavedObjectsClientContract, } from '@kbn/core/server'; import type { DeepReadonly } from 'utility-types'; import type { @@ -107,11 +106,7 @@ export class CspPlugin plugins.fleet.registerExternalCallback( 'packagePolicyCreate', - async ( - packagePolicy: NewPackagePolicy, - _context: RequestHandlerContext, - _request: KibanaRequest - ): Promise => { + async (packagePolicy: NewPackagePolicy): Promise => { const license = await plugins.licensing.refresh(); if (isCspPackage(packagePolicy.package?.name)) { if (!isSubscriptionAllowed(this.isCloudEnabled, license)) { @@ -119,6 +114,10 @@ export class CspPlugin 'To use this feature you must upgrade your subscription or start a trial' ); } + + if (!isSingleEnabledInput(packagePolicy.inputs)) { + throw new Error('Only one enabled input is allowed per policy'); + } } return packagePolicy; @@ -129,12 +128,10 @@ export class CspPlugin 'packagePolicyPostCreate', async ( packagePolicy: PackagePolicy, - context: RequestHandlerContext, - _: KibanaRequest + soClient: SavedObjectsClientContract ): Promise => { if (isCspPackage(packagePolicy.package?.name)) { await this.initialize(core, plugins.taskManager); - const soClient = (await context.core).savedObjects.client; await onPackagePolicyPostCreateCallback(this.logger, packagePolicy, soClient); return packagePolicy; @@ -194,3 +191,6 @@ export class CspPlugin setupFindingsStatsTask(taskManager, coreStartServices, logger); } } + +const isSingleEnabledInput = (inputs: NewPackagePolicy['inputs']): boolean => + inputs.filter((i) => i.enabled).length === 1; diff --git a/x-pack/plugins/cloud_security_posture/server/saved_objects/migrations/csp_rule_template.ts b/x-pack/plugins/cloud_security_posture/server/saved_objects/migrations/csp_rule_template.ts index 470ec21cddc7b..f6f59b21b4a68 100644 --- a/x-pack/plugins/cloud_security_posture/server/saved_objects/migrations/csp_rule_template.ts +++ b/x-pack/plugins/cloud_security_posture/server/saved_objects/migrations/csp_rule_template.ts @@ -43,9 +43,19 @@ function migrateCspRuleTemplatesToV870( ): SavedObjectUnsanitizedDoc { // Keeps only metadata, deprecated state const { muted, enabled, ...attributes } = doc.attributes; + return { ...doc, - attributes, + attributes: { + metadata: { + ...attributes.metadata, + benchmark: { + ...attributes.metadata.benchmark, + // CSPM introduced in 8.7, so we can assume all docs from 8.4.0 are KSPM + posture_type: 'kspm', + }, + }, + }, }; } diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index 75d860bcf75a6..f5b8c48d97a67 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -309,8 +309,7 @@ describe('', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/142774 - describe.skip('detail panel', () => { + describe('detail panel', () => { test('should open a detail panel when clicking on a follower index', async () => { expect(exists('followerIndexDetail')).toBe(false); @@ -334,7 +333,7 @@ describe('', () => { test('should have a "settings" section', async () => { await actions.clickFollowerIndexAt(0); expect(find('followerIndexDetail.settingsSection').find('h3').text()).toEqual('Settings'); - expect(exists('followerIndexDetail.settingsValues')).toBe(true); + expect(find('followerIndexDetail.settingsValues').length).toBeGreaterThan(0); }); test('should set the correct follower index settings values', async () => { diff --git a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts index dd6dd58d02f70..02a9cf8175897 100644 --- a/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts +++ b/x-pack/plugins/enterprise_search/common/__mocks__/initial_app_data.ts @@ -29,6 +29,7 @@ export const DEFAULT_INITIAL_APP_DATA = { }, access: { hasAppSearchAccess: true, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }, appSearch: { diff --git a/x-pack/plugins/enterprise_search/common/types/connectors.ts b/x-pack/plugins/enterprise_search/common/types/connectors.ts index e9bc91892f4f6..87ccbad824e1f 100644 --- a/x-pack/plugins/enterprise_search/common/types/connectors.ts +++ b/x-pack/plugins/enterprise_search/common/types/connectors.ts @@ -17,6 +17,16 @@ export interface ConnectorScheduling { interval: string; } +export interface CustomScheduling { + configuration_overrides: Record; + enabled: boolean; + interval: string; + last_synced: string | null; + name: string; +} + +export type ConnectorCustomScheduling = Record; + export enum ConnectorStatus { CREATED = 'created', NEEDS_CONFIGURATION = 'needs_configuration', @@ -125,6 +135,7 @@ export type ConnectorFeatures = Partial<{ export interface Connector { api_key_id: string | null; configuration: ConnectorConfiguration; + custom_scheduling: ConnectorCustomScheduling; description: string | null; error: string | null; features: ConnectorFeatures; diff --git a/x-pack/plugins/enterprise_search/common/types/extraction_rules.ts b/x-pack/plugins/enterprise_search/common/types/extraction_rules.ts new file mode 100644 index 0000000000000..5bd33bc0af852 --- /dev/null +++ b/x-pack/plugins/enterprise_search/common/types/extraction_rules.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. + */ + +export enum FieldType { + HTML = 'html', + URL = 'url', +} + +export enum ExtractionFilter { + BEGINS = 'begins', + ENDS = 'ends', + CONTAINS = 'contains', + REGEX = 'regex', +} + +export enum ContentFrom { + FIXED = 'fixed', + EXTRACTED = 'extracted', +} + +export enum MultipleObjectsHandling { + ARRAY = 'array', + STRING = 'string', +} + +export interface ExtractionRuleFieldRule { + content_from: { + value: string | null; + value_type: ContentFrom; + }; + field_name: string; + multiple_objects_handling: MultipleObjectsHandling; + selector: string; + source_type: FieldType; +} + +export interface ExtractionRuleBase { + description: string; + rules: ExtractionRuleFieldRule[]; + url_filters: Array<{ filter: ExtractionFilter; pattern: string }>; +} + +export type ExtractionRule = ExtractionRuleBase & { + created_at: string; + domain_id: string; + edited_by: string; + id: string; + updated_at: string; +}; diff --git a/x-pack/plugins/enterprise_search/common/types/index.ts b/x-pack/plugins/enterprise_search/common/types/index.ts index 2e17862edaaf4..e1b0f2045893e 100644 --- a/x-pack/plugins/enterprise_search/common/types/index.ts +++ b/x-pack/plugins/enterprise_search/common/types/index.ts @@ -32,6 +32,7 @@ export interface ConfiguredLimits { export interface ProductAccess { hasAppSearchAccess: boolean; + hasSearchEnginesAccess: boolean; hasWorkplaceSearchAccess: boolean; } diff --git a/x-pack/plugins/enterprise_search/common/types/pagination.ts b/x-pack/plugins/enterprise_search/common/types/pagination.ts index 04f0fa6a46bc7..4dda0f9a4cfcb 100644 --- a/x-pack/plugins/enterprise_search/common/types/pagination.ts +++ b/x-pack/plugins/enterprise_search/common/types/pagination.ts @@ -5,11 +5,17 @@ * 2.0. */ +export interface Page { + from: number; // current page index, 0-based + has_more_hits_than_total?: boolean; + size: number; // size per page + total: number; // total number of hits +} +export interface Meta { + page: Page; +} + export interface Paginate { + _meta: Meta; data: T[]; - has_more_hits_than_total: boolean; - pageIndex: number; - pageSize: number; - size: number; - total: number; } diff --git a/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts b/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts index 9438c0a803f98..6a2a189e0cff9 100644 --- a/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts +++ b/x-pack/plugins/enterprise_search/common/ui_settings_keys.ts @@ -6,4 +6,3 @@ */ export const enterpriseSearchFeatureId = 'enterpriseSearch'; -export const enableEnginesSection = 'enterpriseSearch:enableEnginesSection'; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index dd654fb8357af..4689317cfd61a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -29,6 +29,7 @@ export const mockKibanaValues = { navigateToUrl: jest.fn(), productAccess: { hasAppSearchAccess: true, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }, uiSettings: uiSettingsServiceMock.createStartContract(), diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts index 2555ef905caff..955cf219a8019 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/search_indices.mock.ts @@ -33,6 +33,15 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ connector: { api_key_id: null, configuration: { foo: { label: 'bar', value: 'barbar' } }, + custom_scheduling: { + foo: { + configuration_overrides: {}, + enabled: false, + interval: '', + last_synced: null, + name: '', + }, + }, description: null, error: null, features: null, @@ -119,6 +128,15 @@ export const indices: ElasticsearchIndexWithIngestion[] = [ connector: { api_key_id: null, configuration: { foo: { label: 'bar', value: 'barbar' } }, + custom_scheduling: { + foo: { + configuration_overrides: {}, + enabled: false, + interval: '', + last_synced: null, + name: '', + }, + }, description: null, error: null, features: null, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts index 73e4a65874c05..78ef10664abcb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/__mocks__/view_index.mock.ts @@ -43,6 +43,15 @@ export const connectorIndex: ConnectorViewIndex = { connector: { api_key_id: null, configuration: { foo: { label: 'bar', value: 'barbar' } }, + custom_scheduling: { + foo: { + configuration_overrides: {}, + enabled: false, + interval: '', + last_synced: null, + name: '', + }, + }, description: null, error: null, features: null, @@ -133,6 +142,15 @@ export const crawlerIndex: CrawlerViewIndex = { connector: { api_key_id: null, configuration: { foo: { label: 'bar', value: 'barbar' } }, + custom_scheduling: { + foo: { + configuration_overrides: {}, + enabled: false, + interval: '', + last_synced: null, + name: '', + }, + }, description: null, error: null, features: null, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.test.ts index eadad25b8342a..0b5322107694a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.test.ts @@ -24,18 +24,18 @@ describe('FetchSyncJobs', () => { await nextTick(); expect(http.get).toHaveBeenCalledWith( '/internal/enterprise_search/connectors/connectorId1/sync_jobs', - { query: { page: 0, size: 10 } } + { query: { from: 0, size: 10 } } ); await expect(result).resolves.toEqual('result'); }); it('appends query if specified', async () => { const promise = Promise.resolve('result'); http.get.mockReturnValue(promise); - const result = fetchSyncJobs({ connectorId: 'connectorId1', page: 10, size: 20 }); + const result = fetchSyncJobs({ connectorId: 'connectorId1', from: 10, size: 20 }); await nextTick(); expect(http.get).toHaveBeenCalledWith( '/internal/enterprise_search/connectors/connectorId1/sync_jobs', - { query: { page: 10, size: 20 } } + { query: { from: 10, size: 20 } } ); await expect(result).resolves.toEqual('result'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts index a45bf5e0943ca..c6fb83b172e5e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/fetch_sync_jobs_api_logic.ts @@ -13,15 +13,15 @@ import { HttpLogic } from '../../../shared/http'; export interface FetchSyncJobsArgs { connectorId: string; - page?: number; + from?: number; size?: number; } export type FetchSyncJobsResponse = Paginate; -export const fetchSyncJobs = async ({ connectorId, page = 0, size = 10 }: FetchSyncJobsArgs) => { +export const fetchSyncJobs = async ({ connectorId, from = 0, size = 10 }: FetchSyncJobsArgs) => { const route = `/internal/enterprise_search/connectors/${connectorId}/sync_jobs`; - const query = { page, size }; + const query = { from, size }; return await HttpLogic.values.http.get>(route, { query }); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/_mocks_/crawler_domains.mock.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/_mocks_/crawler_domains.mock.ts index 4b11aa699b081..ad6528ff600aa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/_mocks_/crawler_domains.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/_mocks_/crawler_domains.mock.ts @@ -69,6 +69,7 @@ export const CRAWLER_DOMAIN_FROM_SERVER: CrawlerDomainFromServer = { deduplication_fields: ['url'], document_count: 400, entry_points: [ENTRY_POINT], + extraction_rules: [], id: '123abc', name: 'https://www.elastic.co', sitemaps: [SITEMAP], @@ -101,6 +102,7 @@ export const CRAWLER_DOMAIN: CrawlerDomain = { deduplicationFields: ['url'], documentCount: 400, entryPoints: [ENTRY_POINT], + extractionRules: [], id: '123abc', sitemaps: [SITEMAP], url: 'https://www.elastic.co', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/add_extraction_rule_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/add_extraction_rule_api_logic.test.ts new file mode 100644 index 0000000000000..e564b2717e126 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/add_extraction_rule_api_logic.test.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; you may not use this file except in compliance with the Elastic License + * 2.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 { mockHttpValues } from '../../../../__mocks__/kea_logic'; + +import { addExtractionRule } from './add_extraction_rule_api_logic'; + +describe('AddExtractionRuleApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('addExtractionRule', () => { + it('calls correct api', async () => { + const domainId = 'domain-id'; + const indexName = 'elastic-crawler'; + const rule = { rules: 'fake' } as any; + http.post.mockReturnValue(Promise.resolve('result')); + + const result = addExtractionRule({ + domainId, + indexName, + rule, + }); + expect(http.post).toHaveBeenCalledWith( + `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules`, + { body: JSON.stringify({ extraction_rule: rule }) } + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/add_extraction_rule_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/add_extraction_rule_api_logic.ts new file mode 100644 index 0000000000000..5be30c66ead2f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/add_extraction_rule_api_logic.ts @@ -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 { + ExtractionRule, + ExtractionRuleBase, +} from '../../../../../../common/types/extraction_rules'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; + +import { createApiLogic } from '../../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../../shared/http'; + +export interface AddExtractionRuleArgs { + domainId: string; + indexName: string; + rule: ExtractionRuleBase; +} + +export interface AddExtractionRuleResponse { + extraction_rules: ExtractionRule[]; +} + +export const addExtractionRule = async ({ + domainId, + indexName, + rule: { description, rules, url_filters: urlFilters }, +}: AddExtractionRuleArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules`; + + const params = { + extraction_rule: { + description, + rules, + url_filters: urlFilters, + }, + }; + + return await HttpLogic.values.http.post(route, { + body: JSON.stringify(params), + }); +}; + +export const AddExtractionRuleApiLogic = createApiLogic( + ['add_extraction_rule_api_logic'], + addExtractionRule +); + +export type AddExtractionRuleActions = Actions; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/delete_extraction_rule_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/delete_extraction_rule_api_logic.test.ts new file mode 100644 index 0000000000000..5200a68df84dd --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/delete_extraction_rule_api_logic.test.ts @@ -0,0 +1,41 @@ +/* + * 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. + */ +/* + * 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 { mockHttpValues } from '../../../../__mocks__/kea_logic'; + +import { deleteExtractionRule } from './delete_extraction_rule_api_logic'; + +describe('DeleteExtractionRuleApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('deleteExtractionRule', () => { + it('calls correct api', async () => { + const domainId = 'domain-id'; + const indexName = 'elastic-crawler'; + const extractionRuleId = 'extraction_rule_id'; + http.delete.mockReturnValue(Promise.resolve('result')); + + const result = deleteExtractionRule({ + domainId, + extractionRuleId, + indexName, + }); + expect(http.delete).toHaveBeenCalledWith( + `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules/${extractionRuleId}` + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/delete_extraction_rule_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/delete_extraction_rule_api_logic.ts new file mode 100644 index 0000000000000..63b426a95c9ce --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/delete_extraction_rule_api_logic.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExtractionRule } from '../../../../../../common/types/extraction_rules'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; + +import { createApiLogic } from '../../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../../shared/http'; + +export interface DeleteExtractionRuleArgs { + domainId: string; + extractionRuleId: string; + indexName: string; +} + +export interface DeleteExtractionRuleResponse { + extraction_rules: ExtractionRule[]; +} + +export const deleteExtractionRule = async ({ + domainId, + extractionRuleId, + indexName, +}: DeleteExtractionRuleArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules/${extractionRuleId}`; + + return await HttpLogic.values.http.delete(route); +}; + +export const DeleteExtractionRuleApiLogic = createApiLogic( + ['delete_extraction_rule_api_logic'], + deleteExtractionRule +); + +export type DeleteExtractionRuleActions = Actions< + DeleteExtractionRuleArgs, + DeleteExtractionRuleResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/fetch_extraction_rules_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/fetch_extraction_rules_api_logic.test.ts new file mode 100644 index 0000000000000..d7fb15501ddb6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/fetch_extraction_rules_api_logic.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockHttpValues } from '../../../../__mocks__/kea_logic'; + +import { fetchExtractionRules } from './fetch_extraction_rules_api_logic'; + +describe('FetchExtractionRuleApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('fetchExtractionRule', () => { + it('calls correct api', async () => { + const domainId = 'domain-id'; + const indexName = 'elastic-crawler'; + http.get.mockReturnValue(Promise.resolve('result')); + + const result = fetchExtractionRules({ + domainId, + indexName, + }); + expect(http.get).toHaveBeenCalledWith( + `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules` + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/fetch_extraction_rules_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/fetch_extraction_rules_api_logic.ts new file mode 100644 index 0000000000000..903c4ebb3adfb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/fetch_extraction_rules_api_logic.ts @@ -0,0 +1,37 @@ +/* + * 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 { ExtractionRule } from '../../../../../../common/types/extraction_rules'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; + +import { createApiLogic } from '../../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../../shared/http'; + +export interface FetchExtractionRulesArgs { + domainId: string; + indexName: string; +} + +export interface FetchExtractionRulesResponse { + extraction_rules: ExtractionRule[]; +} + +export const fetchExtractionRules = async ({ domainId, indexName }: FetchExtractionRulesArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules`; + + return await HttpLogic.values.http.get(route); +}; + +export const FetchExtractionRulesApiLogic = createApiLogic( + ['fetch_extraction_rule_api_logic'], + fetchExtractionRules +); + +export type FetchExtractionRulesActions = Actions< + FetchExtractionRulesArgs, + FetchExtractionRulesResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/update_extraction_rule_api_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/update_extraction_rule_api_logic.test.ts new file mode 100644 index 0000000000000..ed08a1e06776a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/update_extraction_rule_api_logic.test.ts @@ -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. + */ +/* + * 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 { mockHttpValues } from '../../../../__mocks__/kea_logic'; + +import { updateExtractionRule } from './update_extraction_rule_api_logic'; + +describe('UpdateExtractionRuleApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('updateExtractionRule', () => { + it('calls correct api', async () => { + const domainId = 'domain-id'; + const extractionRuleId = 'extraction_rule_id'; + const indexName = 'elastic-crawler'; + const rule = { + description: 'haha', + id: extractionRuleId, + rules: ['a'], + url_filters: ['b'], + } as any; + http.put.mockReturnValue(Promise.resolve('result')); + + const result = updateExtractionRule({ + domainId, + indexName, + rule, + }); + expect(http.put).toHaveBeenCalledWith( + `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules/${extractionRuleId}`, + { + body: JSON.stringify({ + extraction_rule: { description: 'haha', rules: ['a'], url_filters: ['b'] }, + }), + } + ); + await expect(result).resolves.toEqual('result'); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/update_extraction_rule_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/update_extraction_rule_api_logic.ts new file mode 100644 index 0000000000000..f36e7833a024e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/extraction_rules/update_extraction_rule_api_logic.ts @@ -0,0 +1,55 @@ +/* + * 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 { + ExtractionRule, + ExtractionRuleBase, +} from '../../../../../../common/types/extraction_rules'; +import { Actions } from '../../../../shared/api_logic/create_api_logic'; + +import { createApiLogic } from '../../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../../shared/http'; + +export interface UpdateExtractionRuleArgs { + domainId: string; + indexName: string; + rule: ExtractionRule; +} + +export interface UpdateExtractionRuleResponse { + extraction_rules: ExtractionRule[]; +} + +export const updateExtractionRule = async ({ + domainId, + indexName, + rule, +}: UpdateExtractionRuleArgs) => { + const route = `/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}/extraction_rules/${rule.id}`; + + const params: { extraction_rule: ExtractionRuleBase } = { + extraction_rule: { + description: rule.description, + rules: rule.rules, + url_filters: rule.url_filters, + }, + }; + + return await HttpLogic.values.http.put(route, { + body: JSON.stringify(params), + }); +}; + +export const UpdateExtractionRuleApiLogic = createApiLogic( + ['update_extraction_rule_api_logic'], + updateExtractionRule +); + +export type UpdateExtractionRuleActions = Actions< + UpdateExtractionRuleArgs, + UpdateExtractionRuleResponse +>; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts index ce941613e3587..4e6f4e2ff0c32 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/types.ts @@ -4,8 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { Meta } from '../../../../../common/types'; import { CrawlerStatus } from '../../../../../common/types/crawler'; +import { ExtractionRule } from '../../../../../common/types/extraction_rules'; // TODO remove this proxy export, which will affect a lot of files export { CrawlerStatus }; @@ -88,6 +90,7 @@ export interface CrawlerDomainFromServer { default_crawl_rule?: CrawlRule; document_count: number; entry_points: EntryPoint[]; + extraction_rules: ExtractionRule[]; id: string; last_visited_at?: string; name: string; @@ -179,6 +182,7 @@ export interface CrawlerDomain { defaultCrawlRule?: CrawlRule; documentCount: number; entryPoints: EntryPoint[]; + extractionRules: ExtractionRule[]; id: string; lastCrawl?: string; sitemaps: Sitemap[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts index 7886d349044c0..1de8addea5afb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/crawler/utils.ts @@ -44,6 +44,7 @@ export function crawlerDomainServerToClient(payload: CrawlerDomainFromServer): C default_crawl_rule: defaultCrawlRule, document_count: documentCount, entry_points: entryPoints, + extraction_rules: extractionRules, id, last_visited_at: lastCrawl, name, @@ -59,6 +60,7 @@ export function crawlerDomainServerToClient(payload: CrawlerDomainFromServer): C deduplicationFields, documentCount, entryPoints, + extractionRules, id, sitemaps, url: name, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/engines/fetch_engines_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/engines/fetch_engines_api_logic.ts index f29edf8ec4206..69e3b5f881666 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/engines/fetch_engines_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/engines/fetch_engines_api_logic.ts @@ -6,14 +6,13 @@ */ import { EnterpriseSearchEnginesResponse } from '../../../../../common/types/engines'; +import { Page } from '../../../../../common/types/pagination'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; -import { Meta } from '../../components/engines/types'; - export interface EnginesListAPIArguments { - meta: Meta; + meta: Page; searchQuery?: string; } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx new file mode 100644 index 0000000000000..852e3698b968c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_flyout.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiFormRow, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { Status } from '../../../../../common/types/api'; +import { isNotNullish } from '../../../../../common/utils/is_not_nullish'; +import { getErrorsFromHttpResponse } from '../../../shared/flash_messages/handle_api_errors'; + +import { + IndicesSelectComboBox, + IndicesSelectComboBoxOption, + indexToOption, +} from '../engines/components/indices_select_combobox'; + +import { AddIndicesLogic } from './add_indices_logic'; + +export interface AddIndicesFlyoutProps { + onClose: () => void; +} + +export const AddIndicesFlyout: React.FC = ({ onClose }) => { + const { selectedIndices, updateEngineStatus, updateEngineError } = useValues(AddIndicesLogic); + const { setSelectedIndices, submitSelectedIndices } = useActions(AddIndicesLogic); + + const selectedOptions = useMemo(() => selectedIndices.map(indexToOption), [selectedIndices]); + const onIndicesChange = useCallback( + (options: IndicesSelectComboBoxOption[]) => { + setSelectedIndices(options.map(({ value }) => value).filter(isNotNullish)); + }, + [setSelectedIndices] + ); + + return ( + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.addIndicesFlyout.title', + { defaultMessage: 'Add new indices' } + )} +

+
+ {updateEngineStatus === Status.ERROR && updateEngineError && ( + <> + + + {getErrorsFromHttpResponse(updateEngineError).map((errMessage, i) => ( +

{errMessage}

+ ))} +
+ + )} +
+ + + + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.addIndicesFlyout.submitButton', + { defaultMessage: 'Add selected' } + )} + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.addIndicesFlyout.cancelButton', + { defaultMessage: 'Cancel' } + )} + + + + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts new file mode 100644 index 0000000000000..cf09b0f80adb1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.test.ts @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { LogicMounter } from '../../../__mocks__/kea_logic'; + +import { Status } from '../../../../../common/types/api'; +import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; + +import { AddIndicesLogic, AddIndicesLogicValues } from './add_indices_logic'; + +const DEFAULT_VALUES: AddIndicesLogicValues = { + selectedIndices: [], + updateEngineError: undefined, + updateEngineStatus: Status.IDLE, +}; + +const makeIndexData = (name: string): ElasticsearchIndexWithIngestion => ({ + count: 0, + hidden: false, + name, + total: { + docs: { count: 0, deleted: 0 }, + store: { size_in_bytes: 'n/a' }, + }, +}); + +describe('AddIndicesLogic', () => { + const { mount: mountAddIndicesLogic } = new LogicMounter(AddIndicesLogic); + const { mount: mountEngineIndicesLogic } = new LogicMounter(AddIndicesLogic); + + beforeEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + + mountAddIndicesLogic(); + mountEngineIndicesLogic(); + }); + + it('has expected default values', () => { + expect(AddIndicesLogic.values).toEqual(DEFAULT_VALUES); + }); + + describe('actions', () => { + describe('setSelectedIndices', () => { + it('adds the indices to selectedIndices', () => { + AddIndicesLogic.actions.setSelectedIndices([ + makeIndexData('index-001'), + makeIndexData('index-002'), + ]); + + expect(AddIndicesLogic.values.selectedIndices).toEqual([ + makeIndexData('index-001'), + makeIndexData('index-002'), + ]); + }); + + it('replaces any existing indices', () => { + AddIndicesLogic.actions.setSelectedIndices([ + makeIndexData('index-001'), + makeIndexData('index-002'), + ]); + AddIndicesLogic.actions.setSelectedIndices([ + makeIndexData('index-003'), + makeIndexData('index-004'), + ]); + + expect(AddIndicesLogic.values.selectedIndices).toEqual([ + makeIndexData('index-003'), + makeIndexData('index-004'), + ]); + }); + }); + }); + + describe('listeners', () => { + describe('engineUpdated', () => { + it('closes the add indices flyout', () => { + jest.spyOn(AddIndicesLogic.actions, 'closeAddIndicesFlyout'); + + AddIndicesLogic.actions.engineUpdated({ + created: '1999-12-31T23:59:59Z', + indices: [], + name: 'engine-name', + updated: '1999-12-31T23:59:59Z', + }); + + expect(AddIndicesLogic.actions.closeAddIndicesFlyout).toHaveBeenCalledTimes(1); + }); + }); + + describe('submitSelectedIndices', () => { + it('does not make a request if there are no selectedIndices', () => { + jest.spyOn(AddIndicesLogic.actions, 'addIndicesToEngine'); + + AddIndicesLogic.actions.submitSelectedIndices(); + + expect(AddIndicesLogic.actions.addIndicesToEngine).toHaveBeenCalledTimes(0); + }); + + it('calls addIndicesToEngine when there are selectedIndices', () => { + jest.spyOn(AddIndicesLogic.actions, 'addIndicesToEngine'); + + AddIndicesLogic.actions.setSelectedIndices([ + makeIndexData('index-001'), + makeIndexData('index-002'), + ]); + AddIndicesLogic.actions.submitSelectedIndices(); + + expect(AddIndicesLogic.actions.addIndicesToEngine).toHaveBeenCalledTimes(1); + expect(AddIndicesLogic.actions.addIndicesToEngine).toHaveBeenCalledWith([ + 'index-001', + 'index-002', + ]); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.ts new file mode 100644 index 0000000000000..37e5cf43ebd00 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/add_indices_logic.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kea, MakeLogicType } from 'kea'; + +import { ElasticsearchIndexWithIngestion } from '../../../../../common/types/indices'; + +import { UpdateEngineApiLogic } from '../../api/engines/update_engine_api_logic'; + +import { EngineIndicesLogic, EngineIndicesLogicActions } from './engine_indices_logic'; + +export interface AddIndicesLogicActions { + addIndicesToEngine: EngineIndicesLogicActions['addIndicesToEngine']; + closeAddIndicesFlyout: EngineIndicesLogicActions['closeAddIndicesFlyout']; + engineUpdated: EngineIndicesLogicActions['engineUpdated']; + setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => { + indices: ElasticsearchIndexWithIngestion[]; + }; + submitSelectedIndices: () => void; +} + +export interface AddIndicesLogicValues { + selectedIndices: ElasticsearchIndexWithIngestion[]; + updateEngineError: typeof UpdateEngineApiLogic.values.error | undefined; + updateEngineStatus: typeof UpdateEngineApiLogic.values.status; +} + +export const AddIndicesLogic = kea>({ + actions: { + setSelectedIndices: (indices: ElasticsearchIndexWithIngestion[]) => ({ indices }), + submitSelectedIndices: () => true, + }, + connect: { + actions: [EngineIndicesLogic, ['addIndicesToEngine', 'engineUpdated', 'closeAddIndicesFlyout']], + values: [UpdateEngineApiLogic, ['status as updateEngineStatus', 'error as updateEngineError']], + }, + listeners: ({ actions, values }) => ({ + engineUpdated: () => { + actions.closeAddIndicesFlyout(); + }, + submitSelectedIndices: () => { + const { selectedIndices } = values; + if (selectedIndices.length === 0) return; + + actions.addIndicesToEngine(selectedIndices.map(({ name }) => name)); + }, + }), + path: ['enterprise_search', 'content', 'add_indices_logic'], + reducers: { + selectedIndices: [ + [], + { + closeAddIndicesFlyout: () => [], + setSelectedIndices: (_, { indices }) => indices, + }, + ], + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx index 00e14382afbb3..0bcdde5e47647 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices.tsx @@ -13,6 +13,8 @@ import { EuiBasicTableColumn, EuiButton, EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, EuiIcon, EuiInMemoryTable, EuiText, @@ -26,20 +28,25 @@ import { indexHealthToHealthColor } from '../../../shared/constants/health_color import { generateEncodedPath } from '../../../shared/encode_path_params'; import { KibanaLogic } from '../../../shared/kibana'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; + import { SEARCH_INDEX_PATH, EngineViewTabs } from '../../routes'; import { IngestionMethod } from '../../types'; import { ingestionMethodToText } from '../../utils/indices'; + import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_template'; +import { AddIndicesFlyout } from './add_indices_flyout'; import { EngineIndicesLogic } from './engine_indices_logic'; -import { EngineViewLogic } from './engine_view_logic'; +import { EngineViewHeaderActions } from './engine_view_header_actions'; export const EngineIndices: React.FC = () => { - const { engineName, isLoadingEngine } = useValues(EngineViewLogic); - const { engineData } = useValues(EngineIndicesLogic); - const { removeIndexFromEngine } = useActions(EngineIndicesLogic); + const { engineData, engineName, isLoadingEngine, addIndicesFlyoutOpen } = + useValues(EngineIndicesLogic); + const { removeIndexFromEngine, openAddIndicesFlyout, closeAddIndicesFlyout } = + useActions(EngineIndicesLogic); const { navigateToUrl } = useValues(KibanaLogic); const [removeIndexConfirm, setConfirmRemoveIndex] = useState(null); + if (!engineData) return null; const { indices } = engineData; @@ -170,11 +177,26 @@ export const EngineIndices: React.FC = () => { defaultMessage: 'Indices', }), rightSideItems: [ - - {i18n.translate('xpack.enterpriseSearch.content.engine.indices.addNewIndicesButton', { - defaultMessage: 'Add new indices', - })} - , + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.indices.addNewIndicesButton', + { + defaultMessage: 'Add new indices', + } + )} + + + + + + , ], }} engineName={engineName} @@ -231,6 +253,7 @@ export const EngineIndices: React.FC = () => { )} + {addIndicesFlyoutOpen && } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.test.ts index e0b29154d9fba..bf088988ed125 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.test.ts @@ -13,8 +13,10 @@ import { FetchEngineApiLogic } from '../../api/engines/fetch_engine_api_logic'; import { EngineIndicesLogic, EngineIndicesLogicValues } from './engine_indices_logic'; const DEFAULT_VALUES: EngineIndicesLogicValues = { + addIndicesFlyoutOpen: false, engineData: undefined, engineName: 'my-test-engine', + isLoadingEngine: true, }; const mockEngineData: EnterpriseSearchEngineDetails = { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.ts index 891d25dc197bb..52c485c8ed7ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_indices_logic.ts @@ -16,15 +16,19 @@ import { EngineViewActions, EngineViewLogic, EngineViewValues } from './engine_v export interface EngineIndicesLogicActions { addIndicesToEngine: (indices: string[]) => { indices: string[] }; + closeAddIndicesFlyout: () => void; engineUpdated: UpdateEngineApiLogicActions['apiSuccess']; fetchEngine: EngineViewActions['fetchEngine']; + openAddIndicesFlyout: () => void; removeIndexFromEngine: (indexName: string) => { indexName: string }; updateEngineRequest: UpdateEngineApiLogicActions['makeRequest']; } export interface EngineIndicesLogicValues { + addIndicesFlyoutOpen: boolean; engineData: EngineViewValues['engineData']; engineName: EngineViewValues['engineName']; + isLoadingEngine: EngineViewValues['isLoadingEngine']; } export const EngineIndicesLogic = kea< @@ -32,6 +36,8 @@ export const EngineIndicesLogic = kea< >({ actions: { addIndicesToEngine: (indices) => ({ indices }), + closeAddIndicesFlyout: () => true, + openAddIndicesFlyout: () => true, removeIndexFromEngine: (indexName) => ({ indexName }), }, connect: { @@ -41,7 +47,7 @@ export const EngineIndicesLogic = kea< UpdateEngineApiLogic, ['makeRequest as updateEngineRequest', 'apiSuccess as engineUpdated'], ], - values: [EngineViewLogic, ['engineData', 'engineName']], + values: [EngineViewLogic, ['engineData', 'engineName', 'isLoadingEngine']], }, listeners: ({ actions, values }) => ({ addIndicesToEngine: ({ indices }) => { @@ -68,4 +74,13 @@ export const EngineIndicesLogic = kea< }, }), path: ['enterprise_search', 'content', 'engine_indices_logic'], + reducers: { + addIndicesFlyoutOpen: [ + false, + { + closeAddIndicesFlyout: () => false, + openAddIndicesFlyout: () => true, + }, + ], + }, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx index 05b34088b1797..e6057a2d1b2bd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view.tsx @@ -15,18 +15,25 @@ import { Status } from '../../../../../common/types/api'; import { KibanaLogic } from '../../../shared/kibana'; import { ENGINE_PATH, EngineViewTabs } from '../../routes'; +import { DeleteEngineModal } from '../engines/delete_engine_modal'; import { EnterpriseSearchEnginesPageTemplate } from '../layout/engines_page_template'; import { EngineAPI } from './engine_api/engine_api'; import { EngineError } from './engine_error'; import { EngineIndices } from './engine_indices'; +import { EngineViewHeaderActions } from './engine_view_header_actions'; import { EngineViewLogic } from './engine_view_logic'; import { EngineHeaderDocsAction } from './header_docs_action'; export const EngineView: React.FC = () => { - const { fetchEngine } = useActions(EngineViewLogic); - const { engineName, fetchEngineApiError, fetchEngineApiStatus, isLoadingEngine } = - useValues(EngineViewLogic); + const { fetchEngine, closeDeleteEngineModal } = useActions(EngineViewLogic); + const { + engineName, + fetchEngineApiError, + fetchEngineApiStatus, + isDeleteModalVisible, + isLoadingEngine, + } = useValues(EngineViewLogic); const { tabId = EngineViewTabs.OVERVIEW } = useParams<{ tabId?: string; }>(); @@ -54,23 +61,28 @@ export const EngineView: React.FC = () => { } return ( - - - - ( - - )} - /> - + <> + {isDeleteModalVisible ? ( + + ) : null} + + + + ( + ], + }} + engineName={engineName} + isLoading={isLoadingEngine} + /> + )} + /> + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx new file mode 100644 index 0000000000000..ba563a9c23dbc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_header_actions.tsx @@ -0,0 +1,80 @@ +/* + * 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 } from 'react'; + +import { useValues, useActions } from 'kea'; + +import { EuiPopover, EuiButtonIcon, EuiText, EuiContextMenu, EuiIcon } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { EngineViewLogic } from './engine_view_logic'; + +export const EngineViewHeaderActions: React.FC = () => { + const { engineData } = useValues(EngineViewLogic); + + const { openDeleteEngineModal } = useActions(EngineViewLogic); + + const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); + const toggleActionsPopover = () => setIsActionsPopoverOpen((isPopoverOpen) => !isPopoverOpen); + const closePopover = () => setIsActionsPopoverOpen(false); + return ( + <> + + } + isOpen={isActionsPopoverOpen} + panelPaddingSize="xs" + closePopover={closePopover} + display="block" + > + , + name: ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.engine.headerActions.delete', + { defaultMessage: 'Delete this engine' } + )} + + ), + onClick: () => { + if (engineData) { + openDeleteEngineModal(); + } + }, + size: 's', + }, + ], + }, + ]} + /> + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.test.ts index 18ad97c54e649..48d85c9f0c6ac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.test.ts @@ -9,6 +9,11 @@ import { LogicMounter } from '../../../__mocks__/kea_logic'; import { Status } from '../../../../../common/types/api'; +import { KibanaLogic } from '../../../shared/kibana'; +import { DeleteEnginesApiLogicResponse } from '../../api/engines/delete_engines_api_logic'; +import { ENGINES_PATH } from '../../routes'; +import { EnginesListLogic } from '../engines/engines_list_logic'; + import { EngineViewLogic, EngineViewValues } from './engine_view_logic'; const DEFAULT_VALUES: EngineViewValues = { @@ -16,19 +21,36 @@ const DEFAULT_VALUES: EngineViewValues = { engineName: 'my-test-engine', fetchEngineApiError: undefined, fetchEngineApiStatus: Status.IDLE, + isDeleteModalVisible: false, isLoadingEngine: true, }; describe('EngineViewLogic', () => { const { mount } = new LogicMounter(EngineViewLogic); + const { mount: mountEnginesListLogic } = new LogicMounter(EnginesListLogic); beforeEach(() => { jest.clearAllMocks(); jest.useRealTimers(); + mountEnginesListLogic(); mount({ engineName: DEFAULT_VALUES.engineName }, { engineName: DEFAULT_VALUES.engineName }); }); it('has expected default values', () => { expect(EngineViewLogic.values).toEqual(DEFAULT_VALUES); }); + + describe('listeners', () => { + describe('deleteSuccess', () => { + it('should navigate to the engines list when an engine is deleted', () => { + jest.spyOn(EngineViewLogic.actions, 'deleteSuccess'); + jest + .spyOn(KibanaLogic.values, 'navigateToUrl') + .mockImplementationOnce(() => Promise.resolve()); + EnginesListLogic.actions.deleteSuccess({} as DeleteEnginesApiLogicResponse); + + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith(ENGINES_PATH); + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.ts index 021ac6d9ea626..1cc3bfc5f4e90 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engine/engine_view_logic.ts @@ -9,15 +9,24 @@ import { kea, MakeLogicType } from 'kea'; import { Status } from '../../../../../common/types/api'; +import { KibanaLogic } from '../../../shared/kibana'; + import { FetchEngineApiLogic, FetchEngineApiLogicActions, } from '../../api/engines/fetch_engine_api_logic'; +import { ENGINES_PATH } from '../../routes'; + +import { EnginesListLogic, EnginesListActions } from '../engines/engines_list_logic'; + import { EngineNameLogic } from './engine_name_logic'; export interface EngineViewActions { + closeDeleteEngineModal(): void; + deleteSuccess: EnginesListActions['deleteSuccess']; fetchEngine: FetchEngineApiLogicActions['makeRequest']; + openDeleteEngineModal(): void; } export interface EngineViewValues { @@ -25,12 +34,18 @@ export interface EngineViewValues { engineName: typeof EngineNameLogic.values.engineName; fetchEngineApiError?: typeof FetchEngineApiLogic.values.error; fetchEngineApiStatus: typeof FetchEngineApiLogic.values.status; + isDeleteModalVisible: boolean; isLoadingEngine: boolean; } export const EngineViewLogic = kea>({ connect: { - actions: [FetchEngineApiLogic, ['makeRequest as fetchEngine']], + actions: [ + FetchEngineApiLogic, + ['makeRequest as fetchEngine'], + EnginesListLogic, + ['deleteSuccess'], + ], values: [ EngineNameLogic, ['engineName'], @@ -38,7 +53,26 @@ export const EngineViewLogic = kea ({ + deleteSuccess: () => { + actions.closeDeleteEngineModal(); + KibanaLogic.values.navigateToUrl(ENGINES_PATH); + }, + }), path: ['enterprise_search', 'content', 'engine_view_logic'], + reducers: () => ({ + isDeleteModalVisible: [ + false, + { + closeDeleteEngineModal: () => false, + openDeleteEngineModal: () => true, + }, + ], + }), selectors: ({ selectors }) => ({ isLoadingEngine: [ () => [selectors.fetchEngineApiStatus, selectors.engineData], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx index 65a9879bb1e5e..5e6f7cebd89fc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/indices_select_combobox.tsx @@ -28,6 +28,8 @@ import { ElasticsearchIndexWithIngestion } from '../../../../../../common/types/ import { indexHealthToHealthColor } from '../../../../shared/constants/health_colors'; import { FetchIndicesForEnginesAPILogic } from '../../../api/engines/fetch_indices_api_logic'; +export type IndicesSelectComboBoxOption = EuiComboBoxOptionOption; + export type IndicesSelectComboBoxProps = Omit< EuiComboBoxProps, 'onCreateOption' | 'onSearchChange' | 'noSuggestions' | 'async' @@ -83,7 +85,7 @@ export const IndicesSelectComboBox = (props: IndicesSelectComboBoxProps) => { export const indexToOption = ( index: ElasticsearchIndexWithIngestion -): EuiComboBoxOptionOption => ({ +): IndicesSelectComboBoxOption => ({ label: index.name, value: index, }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx index 1c419704aa27d..b3c5c46b38128 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/components/tables/engines_table.tsx @@ -20,22 +20,23 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EnterpriseSearchEngine } from '../../../../../../../common/types/engines'; +import { Page } from '../../../../../../../common/types/pagination'; + import { MANAGE_BUTTON_LABEL } from '../../../../../shared/constants'; import { generateEncodedPath } from '../../../../../shared/encode_path_params'; import { FormattedDateTime } from '../../../../../shared/formatted_date_time'; import { KibanaLogic } from '../../../../../shared/kibana'; +import { pageToPagination } from '../../../../../shared/pagination/page_to_pagination'; import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; import { ENGINE_PATH } from '../../../../routes'; -import { convertMetaToPagination, Meta } from '../../types'; - interface EnginesListTableProps { enginesList: EnterpriseSearchEngine[]; isLoading?: boolean; loading: boolean; - meta: Meta; + meta: Page; onChange: (criteria: CriteriaWithPagination) => void; onDelete: (engine: EnterpriseSearchEngine) => void; viewEngineIndices: (engineName: string) => void; @@ -157,7 +158,7 @@ export const EnginesListTable: React.FC = ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx index 16dfdf50a7470..a8cde672107c2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/delete_engine_modal.tsx @@ -15,46 +15,42 @@ import { CANCEL_BUTTON_LABEL } from '../../../shared/constants'; import { EnginesListLogic } from './engines_list_logic'; -export const DeleteEngineModal: React.FC = () => { - const { closeDeleteEngineModal, deleteEngine } = useActions(EnginesListLogic); - const { - deleteModalEngineName: engineName, - isDeleteModalVisible, - isDeleteLoading, - } = useValues(EnginesListLogic); +export interface DeleteEngineModalProps { + engineName: string; + onClose: () => void; +} - if (isDeleteModalVisible) { - return ( - { - deleteEngine({ engineName }); - }} - cancelButtonText={CANCEL_BUTTON_LABEL} - confirmButtonText={i18n.translate( - 'xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title', +export const DeleteEngineModal: React.FC = ({ engineName, onClose }) => { + const { deleteEngine } = useActions(EnginesListLogic); + const { isDeleteLoading } = useValues(EnginesListLogic); + return ( + { + deleteEngine({ engineName }); + }} + cancelButtonText={CANCEL_BUTTON_LABEL} + confirmButtonText={i18n.translate( + 'xpack.enterpriseSearch.content.engineList.deleteEngineModal.confirmButton.title', + { + defaultMessage: 'Yes, delete this engine ', + } + )} + buttonColor="danger" + isLoading={isDeleteLoading} + > +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description', { - defaultMessage: 'Yes, delete this engine ', + defaultMessage: + 'Deleting your engine is not a reversible action. Your indices will not be affected. ', } )} - buttonColor="danger" - isLoading={isDeleteLoading} - > -

- {i18n.translate( - 'xpack.enterpriseSearch.content.engineList.deleteEngineModal.delete.description', - { - defaultMessage: - 'Deleting your engine is not a reversible action. Your indices will not be affected. ', - } - )} -

-
- ); - } else { - return <>; - } +

+
+ ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx index cf97afc5673ec..7165e915fd9e5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list.tsx @@ -47,11 +47,26 @@ const CreateButton: React.FC = () => { }; export const EnginesList: React.FC = () => { - const { closeEngineCreate, fetchEngines, onPaginate, openDeleteEngineModal, setSearchQuery } = - useActions(EnginesListLogic); + const { + closeDeleteEngineModal, + closeEngineCreate, + fetchEngines, + onPaginate, + openDeleteEngineModal, + setSearchQuery, + } = useActions(EnginesListLogic); + const { openFetchEngineFlyout } = useActions(EnginesListFlyoutLogic); - const { isLoading, meta, results, createEngineFlyoutOpen, searchQuery } = - useValues(EnginesListLogic); + + const { + createEngineFlyoutOpen, + deleteModalEngineName, + isDeleteModalVisible, + isLoading, + meta, + results, + searchQuery, + } = useValues(EnginesListLogic); const throttledSearchQuery = useThrottle(searchQuery, INPUT_THROTTLE_DELAY_MS); @@ -61,7 +76,9 @@ export const EnginesList: React.FC = () => { return ( <> - + {isDeleteModalVisible ? ( + + ) : null} {createEngineFlyoutOpen && } diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engine_list_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engine_list_logic.test.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.test.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts index c515ada735d06..d83f5b179a862 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_list_logic.ts @@ -11,8 +11,10 @@ import { Status } from '../../../../../common/types/api'; import { EnterpriseSearchEngine, + EnterpriseSearchEngineDetails, EnterpriseSearchEnginesResponse, } from '../../../../../common/types/engines'; +import { Page } from '../../../../../common/types/pagination'; import { Actions } from '../../../shared/api_logic/create_api_logic'; @@ -26,13 +28,13 @@ import { FetchEnginesAPILogic, } from '../../api/engines/fetch_engines_api_logic'; -import { DEFAULT_META, Meta, updateMetaPageIndex } from './types'; +import { DEFAULT_META, updateMetaPageIndex } from './types'; interface EuiBasicTableOnChange { page: { index: number }; } -type EnginesListActions = Pick< +export type EnginesListActions = Pick< Actions, 'apiError' | 'apiSuccess' | 'makeRequest' > & { @@ -45,10 +47,13 @@ type EnginesListActions = Pick< fetchEngines(): void; onPaginate(args: EuiBasicTableOnChange): { pageNumber: number }; - openDeleteEngineModal: (engine: EnterpriseSearchEngine) => { engine: EnterpriseSearchEngine }; + openDeleteEngineModal: (engine: EnterpriseSearchEngine | EnterpriseSearchEngineDetails) => { + engine: EnterpriseSearchEngine; + }; openEngineCreate(): void; setSearchQuery(searchQuery: string): { searchQuery: string }; }; + interface EngineListValues { createEngineFlyoutOpen: boolean; data: typeof FetchEnginesAPILogic.values.data; @@ -58,8 +63,8 @@ interface EngineListValues { isDeleteLoading: boolean; isDeleteModalVisible: boolean; isLoading: boolean; - meta: Meta; - parameters: { meta: Meta; searchQuery?: string }; // Added this variable to store to the search Query value as well + meta: Page; + parameters: { meta: Page; searchQuery?: string }; // Added this variable to store to the search Query value as well results: EnterpriseSearchEngine[]; // stores engine list value from data searchQuery: string; status: typeof FetchEnginesAPILogic.values.status; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx index 10687556a1e33..fdbc4e544c135 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/engines_router.tsx @@ -10,7 +10,6 @@ import { Route, Switch } from 'react-router-dom'; import { useValues } from 'kea'; -import { enableEnginesSection } from '../../../../../common/ui_settings_keys'; import { KibanaLogic } from '../../../shared/kibana'; import { ENGINES_PATH, ENGINE_PATH } from '../../routes'; @@ -20,8 +19,8 @@ import { NotFound } from '../not_found'; import { EnginesList } from './engines_list'; export const EnginesRouter: React.FC = () => { - const { uiSettings } = useValues(KibanaLogic); - const enginesSectionEnabled = uiSettings?.get(enableEnginesSection, false); + const { productAccess } = useValues(KibanaLogic); + const enginesSectionEnabled = productAccess.hasSearchEnginesAccess; if (!enginesSectionEnabled) { return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/types.ts index 90fb49a054688..ca30dfaea28b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/engines/types.ts @@ -5,11 +5,7 @@ * 2.0. */ -export interface Meta { - from: number; - size: number; - total: number; -} +import { Page } from '../../../../../common/types/pagination'; export const DEFAULT_META = { from: 0, @@ -17,13 +13,6 @@ export const DEFAULT_META = { total: 0, }; -export const convertMetaToPagination = (meta: Meta) => { - return { - pageIndex: meta.from / meta.size, - pageSize: meta.size, - totalItemCount: meta.total, - }; -}; -export const updateMetaPageIndex = (oldState: Meta, newPageIndex: number) => { +export const updateMetaPageIndex = (oldState: Page, newPageIndex: number) => { return { ...oldState, from: newPageIndex * oldState.size }; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel.tsx index 4f406ebe67939..4fe77b0196771 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/authentication_panel/authentication_panel.tsx @@ -29,7 +29,7 @@ export const AuthenticationPanel: React.FC = () => {
- +

{i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.title', { defaultMessage: 'Authentication', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx index 6bead7b4314d9..db98f1fb987f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawl_rules_table.tsx @@ -251,9 +251,7 @@ export const CrawlRulesTable: React.FC = ({ updateCrawlRules(newCrawlRules as CrawlRule[]); clearFlashMessages(); }} - title={i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.title', { - defaultMessage: 'Crawl rules', - })} + title="" uneditableItems={defaultCrawlRule ? [defaultCrawlRule] : undefined} canRemoveLastItem /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_logic.ts index b36671d492714..d17f3df02550f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_logic.ts @@ -7,7 +7,8 @@ import { kea, MakeLogicType } from 'kea'; -import { HttpError, Status } from '../../../../../../../common/types/api'; +import { Status } from '../../../../../../../common/types/api'; +import { ExtractionRule } from '../../../../../../../common/types/extraction_rules'; import { generateEncodedPath } from '../../../../../shared/encode_path_params'; @@ -42,11 +43,11 @@ export interface CrawlerDomainDetailValues { deleteStatus: Status; domain: CrawlerDomain | null; domainId: string; + extractionRules: ExtractionRule[]; getLoading: boolean; } export interface CrawlerDomainDetailActions { - deleteApiError(error: HttpError): HttpError; deleteApiSuccess(response: DeleteCrawlerDomainResponse): DeleteCrawlerDomainResponse; deleteDomain(): void; deleteMakeRequest(args: DeleteCrawlerDomainArgs): DeleteCrawlerDomainArgs; @@ -59,24 +60,13 @@ export interface CrawlerDomainDetailActions { }; updateCrawlRules(crawlRules: CrawlRule[]): { crawlRules: CrawlRule[] }; updateEntryPoints(entryPoints: EntryPoint[]): { entryPoints: EntryPoint[] }; + updateExtractionRules(extractionRules: ExtractionRule[]): { extractionRules: ExtractionRule[] }; updateSitemaps(entryPoints: Sitemap[]): { sitemaps: Sitemap[] }; } export const CrawlerDomainDetailLogic = kea< MakeLogicType >({ - path: ['enterprise_search', 'crawler', 'crawler_domain_detail_logic'], - connect: { - actions: [ - DeleteCrawlerDomainApiLogic, - [ - 'apiError as deleteApiError', - 'apiSuccess as deleteApiSuccess', - 'makeRequest as deleteMakeRequest', - ], - ], - values: [DeleteCrawlerDomainApiLogic, ['status as deleteStatus']], - }, actions: { deleteDomain: () => true, deleteDomainComplete: () => true, @@ -86,36 +76,26 @@ export const CrawlerDomainDetailLogic = kea< submitDeduplicationUpdate: ({ fields, enabled }) => ({ enabled, fields }), updateCrawlRules: (crawlRules) => ({ crawlRules }), updateEntryPoints: (entryPoints) => ({ entryPoints }), + updateExtractionRules: (extractionRules) => ({ extractionRules }), updateSitemaps: (sitemaps) => ({ sitemaps }), }, - reducers: ({ props }) => ({ - domain: [ - null, - { - receiveDomainData: (_, { domain }) => domain, - updateCrawlRules: (currentDomain, { crawlRules }) => - ({ ...currentDomain, crawlRules } as CrawlerDomain), - updateEntryPoints: (currentDomain, { entryPoints }) => - ({ ...currentDomain, entryPoints } as CrawlerDomain), - updateSitemaps: (currentDomain, { sitemaps }) => - ({ ...currentDomain, sitemaps } as CrawlerDomain), - }, - ], - domainId: [props.domainId, { fetchDomainData: (_, { domainId }) => domainId }], - getLoading: [ - true, - { - receiveDomainData: () => false, - }, - ], - }), - selectors: ({ selectors }) => ({ - deleteLoading: [ - () => [selectors.deleteStatus], - (deleteStatus: Status) => deleteStatus === Status.LOADING, + connect: { + actions: [ + DeleteCrawlerDomainApiLogic, + ['apiSuccess as deleteApiSuccess', 'makeRequest as deleteMakeRequest'], ], - }), + values: [DeleteCrawlerDomainApiLogic, ['status as deleteStatus']], + }, listeners: ({ actions, values }) => ({ + deleteApiSuccess: () => { + const { indexName } = IndexNameLogic.values; + KibanaLogic.values.navigateToUrl( + generateEncodedPath(SEARCH_INDEX_TAB_PATH, { + indexName, + tabId: SearchIndexTabId.DOMAIN_MANAGEMENT, + }) + ); + }, deleteDomain: async () => { const { domain } = values; const { indexName } = IndexNameLogic.values; @@ -126,15 +106,6 @@ export const CrawlerDomainDetailLogic = kea< }); } }, - deleteApiSuccess: () => { - const { indexName } = IndexNameLogic.values; - KibanaLogic.values.navigateToUrl( - generateEncodedPath(SEARCH_INDEX_TAB_PATH, { - indexName, - tabId: SearchIndexTabId.DOMAIN_MANAGEMENT, - }) - ); - }, fetchDomainData: async ({ domainId }) => { const { http } = HttpLogic.values; const { indexName } = IndexNameLogic.values; @@ -201,4 +172,36 @@ export const CrawlerDomainDetailLogic = kea< } }, }), + path: ['enterprise_search', 'crawler', 'crawler_domain_detail_logic'], + reducers: ({ props }) => ({ + domain: [ + null, + { + receiveDomainData: (_, { domain }) => domain, + updateCrawlRules: (currentDomain, { crawlRules }) => + currentDomain ? { ...currentDomain, crawlRules } : currentDomain, + updateEntryPoints: (currentDomain, { entryPoints }) => + currentDomain ? { ...currentDomain, entryPoints } : currentDomain, + updateSitemaps: (currentDomain, { sitemaps }) => + currentDomain ? { ...currentDomain, sitemaps } : currentDomain, + }, + ], + domainId: [props.domainId, { fetchDomainData: (_, { domainId }) => domainId }], + getLoading: [ + true, + { + receiveDomainData: () => false, + }, + ], + }), + selectors: ({ selectors }) => ({ + deleteLoading: [ + () => [selectors.deleteStatus], + (deleteStatus: Status) => deleteStatus === Status.LOADING, + ], + extractionRules: [ + () => [selectors.domain], + (domain: CrawlerDomain | null) => domain?.extractionRules ?? [], + ], + }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx index a62dc8bf3d558..9ca36ba97f7b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/crawler_domain_detail_tabs.tsx @@ -7,7 +7,7 @@ import React, { useState } from 'react'; -import { EuiSpacer, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; +import { EuiSpacer, EuiTabbedContent, EuiTabbedContentTab, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { CrawlerDomain } from '../../../../api/crawler/types'; @@ -16,6 +16,7 @@ import { AuthenticationPanel } from './authentication_panel/authentication_panel import { CrawlRulesTable } from './crawl_rules_table'; import { DeduplicationPanel } from './deduplication_panel/deduplication_panel'; import { EntryPointsTable } from './entry_points_table'; +import { ExtractionRules } from './extraction_rules/extraction_rules'; import { SitemapsTable } from './sitemaps_table'; export enum CrawlerDomainTabId { @@ -23,6 +24,7 @@ export enum CrawlerDomainTabId { AUTHENTICATION = 'authentication', SITE_MAPS = 'site_maps', CRAWL_RULES = 'crawl_rules', + EXTRACTION_RULES = 'extraction_rules', DEDUPLICATION = 'deduplication', } @@ -41,6 +43,13 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> + +

+ {i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.title', { + defaultMessage: 'Entry points', + })} +

+
), @@ -65,6 +74,13 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> + +

+ {i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.title', { + defaultMessage: 'Sitemaps', + })} +

+
), @@ -77,6 +93,13 @@ export const CrawlerDomainDetailTabs: React.FC = ( content: ( <> + +

+ {i18n.translate('xpack.enterpriseSearch.crawler.crawlRulesTable.title', { + defaultMessage: 'Crawl rules', + })} +

+
= ( defaultMessage: 'Crawl rules', }), }, + { + content: ( + <> + + + + ), + id: CrawlerDomainTabId.EXTRACTION_RULES, + name: i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules', { + defaultMessage: 'Extraction rules', + }), + }, { content: , id: CrawlerDomainTabId.DEDUPLICATION, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx index 8076b3e49aa1f..e17815765169e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/deduplication_panel/deduplication_panel.tsx @@ -57,7 +57,7 @@ export const DeduplicationPanel: React.FC = () => { - +

{i18n.translate('xpack.enterpriseSearch.crawler.deduplicationPanel.title', { defaultMessage: 'Duplicate document handling', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.test.tsx index cf1224cb0dc47..438792b037ff9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.test.tsx @@ -33,6 +33,7 @@ describe('EntryPointsTable', () => { deduplicationFields: ['title'], documentCount: 10, entryPoints, + extractionRules: [], id: '6113e1407a2f2e6f42489794', sitemaps: [], url: 'https://www.elastic.co', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.tsx index a80ecc85646b3..8a38abf4efdc7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/entry_points_table.tsx @@ -130,9 +130,7 @@ export const EntryPointsTable: React.FC = ({ domain, inde onAdd={onAdd} onDelete={onDelete} onUpdate={onUpdate} - title={i18n.translate('xpack.enterpriseSearch.crawler.entryPointsTable.title', { - defaultMessage: 'Entry points', - })} + title="" disableReordering /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/content_fields_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/content_fields_panel.tsx new file mode 100644 index 0000000000000..fae0089669538 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/content_fields_panel.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 { + EuiEmptyPrompt, + EuiText, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { ExtractionRuleFieldRule } from '../../../../../../../../common/types/extraction_rules'; + +import { FieldRulesTable } from './field_rules_table'; + +interface ContentFieldsPanelProps { + contentFields: Array; + editExistingField: (id: string) => void; + editNewField: () => void; + removeField: (id: string) => void; +} + +export const ContentFieldsPanel: React.FC = ({ + contentFields, + editNewField, + editExistingField, + removeField, +}) => { + return contentFields.length === 0 ? ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.fieldRules.emptyMessageTitle', + { + defaultMessage: 'This extraction rule has no content fields', + } + )} +

+ } + titleSize="s" + body={ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.fieldRules.emptyMessageDescription', + { + defaultMessage: + 'Create a content field to pinpoint which parts of a webpage to pull data from.', + } + )} + + } + actions={ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.fieldRules.emptyMessageAddRuleLabel', + { + defaultMessage: 'Add content fields', + } + )} + + } + /> + ) : ( + <> + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.fieldRules.contentFieldDescription', + { + defaultMessage: + 'Create a content field to pinpoint which parts of a webpage to pull data from.', + } + )} +

+
+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.fieldRules.addContentFieldRuleLabel', + { + defaultMessage: 'Add content field rule', + } + )} + + +
+ + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_extraction_rule.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_extraction_rule.tsx new file mode 100644 index 0000000000000..c03a395591bae --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_extraction_rule.tsx @@ -0,0 +1,446 @@ +/* + * 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, useState } from 'react'; + +import { Controller, useFieldArray, useForm } from 'react-hook-form'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiButtonEmpty, + EuiButtonIcon, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiLink, + EuiPanel, + EuiRadioGroup, + EuiSelect, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { + ExtractionFilter, + ExtractionRule, + ExtractionRuleBase, +} from '../../../../../../../../common/types/extraction_rules'; + +import { ContentFieldsPanel } from './content_fields_panel'; +import { EditFieldRuleFlyout } from './edit_field_rule_flyout'; +import { ExtractionRulesLogic } from './extraction_rules_logic'; + +interface EditExtractionRuleProps { + cancelEditing: () => void; + extractionRule: ExtractionRule | null; + isNewRule: boolean; + saveRule: (rule: ExtractionRuleBase) => void; +} + +enum UrlState { + ALL = 'all', + SPECIFIC = 'specific', +} + +const getReadableExtractionFilter = (rule: ExtractionFilter) => { + switch (rule) { + case ExtractionFilter.BEGINS: + return i18n.translate( + 'xpack.enterpriseSearch.crawler.extractionRulesExtractionFilter.beginsWithLabel', + { + defaultMessage: 'Begins with', + } + ); + case ExtractionFilter.ENDS: + return i18n.translate( + 'xpack.enterpriseSearch.crawler.extractionRulesExtractionFilter.endsWithLabel', + { + defaultMessage: 'Ends with', + } + ); + case ExtractionFilter.CONTAINS: + return i18n.translate( + 'xpack.enterpriseSearch.crawler.extractionRulesExtractionFilter.containsLabel', + { + defaultMessage: 'Contains', + } + ); + case ExtractionFilter.REGEX: + return i18n.translate( + 'xpack.enterpriseSearch.crawler.extractionRulesExtractionFilter.regexLabel', + { + defaultMessage: 'Regex', + } + ); + } +}; + +const extractionFilterOptions = [ + ExtractionFilter.BEGINS, + ExtractionFilter.ENDS, + ExtractionFilter.CONTAINS, + ExtractionFilter.REGEX, +].map((ruleOption: ExtractionFilter) => ({ + text: getReadableExtractionFilter(ruleOption), + value: ruleOption, +})); + +export const EditExtractionRule: React.FC = ({ + cancelEditing, + extractionRule, + isNewRule, + saveRule, +}) => { + const { closeEditRuleFlyout, openEditRuleFlyout } = useActions(ExtractionRulesLogic); + const { fieldRuleFlyoutVisible, fieldRuleToEdit, fieldRuleToEditIndex, fieldRuleToEditIsNew } = + useValues(ExtractionRulesLogic); + const [urlToggle, setUrlToggle] = useState(UrlState.ALL); + const { control, formState, getValues, handleSubmit, reset, setValue } = + useForm({ + defaultValues: extractionRule ?? { + description: '', + rules: [], + url_filters: [], + }, + mode: 'all', + }); + const { + append: appendUrlFilter, + fields: urlFiltersFields, + remove: removeUrlFilter, + } = useFieldArray({ + control, + name: 'url_filters', + }); + const { + append: appendRule, + fields: rulesFields, + remove: removeRule, + update: updateRule, + } = useFieldArray({ control, name: 'rules' }); + + useEffect(() => { + reset( + extractionRule ?? { + description: '', + rules: [], + url_filters: [], + } + ); + if (extractionRule) { + setUrlToggle(extractionRule.url_filters.length === 0 ? UrlState.ALL : UrlState.SPECIFIC); + } else { + setUrlToggle(UrlState.ALL); + } + }, [extractionRule]); + + return ( + <> + +

+ {isNewRule + ? i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.addRule.title', + { + defaultMessage: 'Create a content extraction rule', + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.title', + { + defaultMessage: 'Edit content extraction rule', + } + )} +

+
+ + + { + if (!rule?.trim()) { + return i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.descriptionError', + { + defaultMessage: 'A description is required for a content extraction rule', + } + ); + } + }, + }} + render={({ field, fieldState }) => ( + + + + )} + /> + + { + setUrlToggle(value as UrlState); + // Make sure we always have one url filter when switching to specific URL filters + if (value === UrlState.SPECIFIC && urlFiltersFields.length < 1) { + setValue('url_filters', [{ filter: ExtractionFilter.BEGINS, pattern: '' }]); + } else { + setValue('url_filters', []); + } + }} + /> + + + {urlToggle === UrlState.SPECIFIC && ( + <> + {urlFiltersFields.map((urlFilter, index) => ( + + + ( + + + + )} + /> + + + ( + <> + + + + + + )} + /> + + + {urlFiltersFields.length > 1 && ( + removeUrlFilter(index)} + /> + )} + + + ))} + + appendUrlFilter({ filter: ExtractionFilter.BEGINS, pattern: '' })} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.url.urlFilters.addFilter', + { + defaultMessage: 'Add URL filter', + } + )} + + + )} + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.url.urlFiltersLink', + { + defaultMessage: 'Learn more about URL filters', + } + )} + + + + + openEditRuleFlyout({ + fieldRule: rulesFields.find(({ id: ruleId }) => ruleId === id), + isNewRule: false, + }) + } + editNewField={() => openEditRuleFlyout({ isNewRule: true })} + removeField={(id) => { + const index = rulesFields.findIndex(({ id: ruleId }) => ruleId === id); + if (index >= 0) { + removeRule(index); + } + }} + /> + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + + + + saveRule({ ...getValues() })} + disabled={!formState.isValid} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editRule.saveButtonLabel', + { + defaultMessage: 'Save rule', + } + )} + + + + + + {fieldRuleFlyoutVisible && ( + { + if (fieldRuleToEditIsNew) { + appendRule(fieldRule); + } else { + updateRule(fieldRuleToEditIndex ?? 0, fieldRule); + } + closeEditRuleFlyout(); + }} + /> + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx new file mode 100644 index 0000000000000..2bb28f0d27045 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/edit_field_rule_flyout.tsx @@ -0,0 +1,489 @@ +/* + * 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 { Controller, useForm } from 'react-hook-form'; + +import { + EuiButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiForm, + EuiFormRow, + EuiPanel, + EuiRadioGroup, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { + ContentFrom, + ExtractionRuleFieldRule, + FieldType, + MultipleObjectsHandling, +} from '../../../../../../../../common/types/extraction_rules'; + +interface EditFieldRuleFlyoutProps { + fieldRule: ExtractionRuleFieldRule | null; + isNewRule: boolean; + onClose: () => void; + saveRule: (fieldRule: ExtractionRuleFieldRule & { id?: string; index?: number }) => void; +} + +const defaultRule = { + content_from: { + value: '', + value_type: undefined, + }, + field_name: '', + multiple_objects_handling: MultipleObjectsHandling.STRING, + selector: '', + source_type: undefined, +}; + +export const EditFieldRuleFlyout: React.FC = ({ + onClose, + fieldRule, + isNewRule, + saveRule, +}) => { + const { control, reset, getValues, formState } = useForm({ + defaultValues: fieldRule ?? defaultRule, + mode: 'all', + }); + + useEffect(() => { + reset(fieldRule ?? defaultRule); + }, [fieldRule]); + + return ( + + + +

+ {isNewRule + ? i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.addContentField.title', + { + defaultMessage: 'Add content field rule', + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.title', + { + defaultMessage: 'Edit content field rule', + } + )} +

+
+
+ + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.documentField.title', + { + defaultMessage: 'Document field', + } + )} +

+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.documentField.description', + { + defaultMessage: 'Select a document field to build a rule around.', + } + )} + + + + !!rule?.trim() || + i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.edilidtContentField.documentField.requiredError', + { + defaultMessage: 'A field name is required.', + } + ), + }} + render={({ field, fieldState: { error, isTouched } }) => ( + + + + )} + /> +
+ + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.source.title', + { + defaultMessage: 'Source', + } + )} +

+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.source.description', + { + defaultMessage: 'Where to extract the content for this field from.', + } + )} + + + + !!rule?.trim() || + i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.source.requiredError', + { + defaultMessage: 'A source for the content is required.', + } + ), + }} + render={({ field }) => ( + <> + + + + {!!field.value && ( + <> + + + ( + + )} + /> + + + )} + + )} + /> +
+ + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.content.title', + { + defaultMessage: 'Content', + } + )} +

+
+ + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.content.description', + { + defaultMessage: 'Populate the field with content.', + } + )} + + + + + !!field?.trim() || + i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.content.requiredError', + { + defaultMessage: 'A value for this content field is required', + } + ), + }} + render={({ field, fieldState: { error, isTouched } }) => ( + <> + + + + {field.value === ContentFrom.EXTRACTED ? ( + ( + <> + + + + + + )} + /> + ) : ( + field.value === ContentFrom.FIXED && ( + <> + + ( + + + + )} + /> + + ) + )} + + )} + /> +
+
+
+ + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.cancelButton.label', + { + defaultMessage: 'Cancel', + } + )} + + + + { + saveRule({ ...getValues() }); + }} + fill + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.extractionRules.editContentField.saveButton.label', + { + defaultMessage: 'Save', + } + )} + + + + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx new file mode 100644 index 0000000000000..5f3f734c24cd9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules.tsx @@ -0,0 +1,183 @@ +/* + * 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 { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiConfirmModal, + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { CANCEL_BUTTON_LABEL } from '../../../../../../shared/constants'; + +import { EditExtractionRule } from './edit_extraction_rule'; +import { ExtractionRulesLogic } from './extraction_rules_logic'; +import { ExtractionRulesTable } from './extraction_rules_table'; + +export const ExtractionRules: React.FC = () => { + const { + cancelEditExtractionRule, + deleteExtractionRule, + editNewExtractionRule, + hideDeleteModal, + saveExtractionRule, + } = useActions(ExtractionRulesLogic); + const { + deleteModalVisible, + editingExtractionRule, + extractionRules, + extractionRuleToDelete, + extractionRuleToEdit, + extractionRuleToEditIsNew, + } = useValues(ExtractionRulesLogic); + + return ( + <> + {deleteModalVisible && ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.deleteModal.description', + { + defaultMessage: + 'Removing this rule will also delete {fields, plural, one {one field rule} other {# field rules}}. This action cannot be undone.', + values: { fields: extractionRuleToDelete?.rules.length ?? 0 }, + } + )} + + )} + + + +

+ {i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.title', { + defaultMessage: 'Extraction rules', + })} +

+
+
+ {extractionRules.length === 0 ? ( + <> + ) : ( + + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.addRuleLabel', + { + defaultMessage: 'Add extraction rule', + } + )} + + + )} +
+ + +

+ + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.learnMoreLink', + { + defaultMessage: 'Learn more about content extraction rules.', + } + )} + + ), + }} + /> +

+
+ {editingExtractionRule ? ( + + ) : extractionRules.length === 0 ? ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.emptyMessageTitle', + { + defaultMessage: 'There are no content extraction rules', + } + )} +

+ } + titleSize="s" + body={ + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.emptyMessageDescription', + { + defaultMessage: + 'Create a content extraction rule to change where document fields get their data during a sync.', + } + )} + + } + actions={ + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRulesTable.emptyMessageAddRuleLabel', + { + defaultMessage: 'Add content extraction rule', + } + )} + + } + /> + ) : ( + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_logic.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_logic.tsx new file mode 100644 index 0000000000000..f44d83cb7c99d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_logic.tsx @@ -0,0 +1,327 @@ +/* + * 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 { kea, MakeLogicType } from 'kea'; + +import { Status } from '../../../../../../../../common/types/api'; +import { + ExtractionRule, + ExtractionRuleBase, + ExtractionRuleFieldRule, +} from '../../../../../../../../common/types/extraction_rules'; +import { + AddExtractionRuleActions, + AddExtractionRuleApiLogic, +} from '../../../../../api/crawler/extraction_rules/add_extraction_rule_api_logic'; +import { + DeleteExtractionRuleActions, + DeleteExtractionRuleApiLogic, +} from '../../../../../api/crawler/extraction_rules/delete_extraction_rule_api_logic'; +import { + FetchExtractionRulesActions, + FetchExtractionRulesApiLogic, +} from '../../../../../api/crawler/extraction_rules/fetch_extraction_rules_api_logic'; +import { + UpdateExtractionRuleActions, + UpdateExtractionRuleApiLogic, +} from '../../../../../api/crawler/extraction_rules/update_extraction_rule_api_logic'; +import { IndexNameLogic } from '../../../index_name_logic'; + +import { + CrawlerDomainDetailActions, + CrawlerDomainDetailLogic, + CrawlerDomainDetailValues, +} from '../crawler_domain_detail_logic'; + +export type ExtractionRuleView = ExtractionRule & { isExpanded: boolean }; + +interface ExtractionRulesActions { + addExtractionRule: AddExtractionRuleActions['makeRequest']; + addExtractionRuleSuccess: AddExtractionRuleActions['apiSuccess']; + applyDraft: () => void; + cancelEditExtractionRule: () => void; + closeEditRuleFlyout: () => void; + deleteExtractionRule: () => void; + deleteExtractionRuleRequest: DeleteExtractionRuleActions['makeRequest']; + deleteExtractionRuleSuccess: DeleteExtractionRuleActions['apiSuccess']; + deleteFieldRule: () => void; + editExtractionRule(extractionRule: ExtractionRule): { extractionRule: ExtractionRule }; + editNewExtractionRule: () => void; + fetchExtractionRules: FetchExtractionRulesActions['makeRequest']; + fetchExtractionRulesSuccess: FetchExtractionRulesActions['apiSuccess']; + hideDeleteFieldModal: () => void; + hideDeleteModal: () => void; + openEditRuleFlyout({ + fieldRule, + fieldRuleIndex, + isNewRule, + }: { + fieldRule?: ExtractionRuleFieldRule; + fieldRuleIndex?: number; + isNewRule: boolean; + }): { + fieldRule: ExtractionRuleFieldRule; + fieldRuleIndex?: number; + isNewRule: boolean; + }; + fetchDomainData: CrawlerDomainDetailActions['fetchDomainData']; + saveExtractionRule(extractionRule: ExtractionRuleBase): { + extractionRule: ExtractionRuleBase; + }; + setLocalExtractionRules(extractionRules: ExtractionRule[]): { extractionRules: ExtractionRule[] }; + showDeleteFieldModal({ + fieldRuleIndex, + extractionRuleId, + }: { + extractionRuleId: string; + fieldRuleIndex: number; + }): { extractionRuleId: string; fieldRuleIndex: number }; + showDeleteModal(extractionRule: ExtractionRule): { extractionRule: ExtractionRule }; + updateExtractionRule: UpdateExtractionRuleActions['makeRequest']; + updateExtractionRuleSuccess: UpdateExtractionRuleActions['apiSuccess']; +} + +interface ExtractionRulesValues { + addStatus: Status; + deleteFieldModalVisible: boolean; + deleteModalVisible: boolean; + deleteStatus: Status; + domain: CrawlerDomainDetailValues['domain']; + domainExtractionRules: ExtractionRule[] | null; + domainId: string; + editingExtractionRule: boolean; + extractionRuleToDelete: ExtractionRule | null; + extractionRuleToEdit: ExtractionRule | null; + extractionRuleToEditIsNew: boolean; + extractionRules: ExtractionRule[]; + fieldRuleFlyoutVisible: boolean; + fieldRuleToDelete: { extractionRuleId?: string; fieldRuleIndex?: number }; + fieldRuleToEdit: ExtractionRuleFieldRule | null; + fieldRuleToEditIndex: number | null; + fieldRuleToEditIsNew: boolean; + indexName: string; + isLoading: boolean; + isLoadingUpdate: boolean; + jsonValidationError: boolean; + updateStatus: Status; + updatedExtractionRules: ExtractionRule[] | null; +} + +export const ExtractionRulesLogic = kea< + MakeLogicType +>({ + actions: { + cancelEditExtractionRule: true, + closeEditRuleFlyout: true, + deleteExtractionRule: true, + deleteFieldRule: true, + editExtractionRule: (extractionRule) => ({ extractionRule }), + editNewExtractionRule: true, + hideDeleteFieldModal: true, + hideDeleteModal: true, + openEditRuleFlyout: ({ fieldRule, isNewRule }) => ({ + fieldRule, + isNewRule, + }), + saveExtractionRule: (extractionRule: ExtractionRuleBase) => ({ extractionRule }), + showDeleteFieldModal: ({ fieldRuleIndex, extractionRuleId }) => ({ + extractionRuleId, + fieldRuleIndex, + }), + showDeleteModal: (extractionRule: ExtractionRule) => ({ extractionRule }), + }, + connect: { + actions: [ + AddExtractionRuleApiLogic, + ['makeRequest as addExtractionRule', 'apiSuccess as addExtractionRuleSuccess'], + CrawlerDomainDetailLogic, + ['receiveDomainData'], + DeleteExtractionRuleApiLogic, + ['makeRequest as deleteExtractionRuleRequest', 'apiSuccess as deleteExtractionRuleSuccess'], + FetchExtractionRulesApiLogic, + ['makeRequest as fetchExtractionRules', 'apiSuccess as fetchExtractionRulesSuccess'], + UpdateExtractionRuleApiLogic, + ['makeRequest as updateExtractionRule', 'apiSuccess as updateExtractionRuleSuccess'], + ], + values: [ + AddExtractionRuleApiLogic, + ['status as addStatus'], + CrawlerDomainDetailLogic, + ['domain', 'domainId', 'extractionRules as domainExtractionRules', 'getLoading as isLoading'], + DeleteExtractionRuleApiLogic, + ['status as deleteStatus'], + IndexNameLogic, + ['indexName'], + UpdateExtractionRuleApiLogic, + ['status as updateStatus'], + ], + }, + events: ({ actions, values }) => ({ + beforeUnmount: () => { + // This prevents stale data from hanging around on unload + actions.fetchDomainData(values.domainId); + }, + }), + listeners: ({ actions, values }) => ({ + deleteExtractionRule: () => { + if (values.extractionRuleToDelete) { + actions.deleteExtractionRuleRequest({ + domainId: values.domainId, + extractionRuleId: values.extractionRuleToDelete?.id, + indexName: values.indexName, + }); + } + }, + deleteExtractionRuleSuccess: () => { + actions.hideDeleteModal(); + }, + deleteFieldRule: () => { + const { extractionRuleId, fieldRuleIndex } = values.fieldRuleToDelete; + const extractionRule = values.extractionRules.find(({ id }) => id === extractionRuleId); + if (extractionRule) { + const newFieldRules = extractionRule.rules.filter((_, index) => index !== fieldRuleIndex); + actions.updateExtractionRule({ + domainId: values.domainId, + indexName: values.indexName, + rule: { ...extractionRule, rules: newFieldRules }, + }); + } + }, + saveExtractionRule: ({ extractionRule }) => { + if (values.extractionRuleToEditIsNew) { + actions.addExtractionRule({ + domainId: values.domainId, + indexName: values.indexName, + rule: extractionRule, + }); + } else if (values.extractionRuleToEdit) { + actions.updateExtractionRule({ + domainId: values.domainId, + indexName: values.indexName, + rule: { ...values.extractionRuleToEdit, ...extractionRule }, + }); + } + }, + }), + path: ['enterprise_search', 'content', 'crawler', 'extraction_rules'], + reducers: () => ({ + deleteFieldModalVisible: [ + false, + { + hideDeleteFieldModal: () => false, + showDeleteFieldModal: () => true, + updateExtractionRuleSuccess: () => false, + }, + ], + deleteModalVisible: [ + false, + { + deleteExtractionRuleSuccess: () => false, + hideDeleteModal: () => false, + showDeleteModal: () => true, + }, + ], + editingExtractionRule: [ + false, + { + addExtractionRuleSuccess: () => false, + cancelEditExtractionRule: () => false, + editExtractionRule: () => true, + editNewExtractionRule: () => true, + updateExtractionRuleSuccess: () => false, + }, + ], + extractionRuleToDelete: [ + null, + { + deleteExtractionRuleSuccess: () => null, + hideDeleteModal: () => null, + showDeleteModal: (_, { extractionRule }) => extractionRule, + }, + ], + extractionRuleToEdit: [ + null, + { + addSuccess: () => null, + cancelEditExtractionRule: () => null, + editExtractionRule: (_, { extractionRule }) => extractionRule, + updateSuccess: () => null, + }, + ], + extractionRuleToEditIsNew: [ + false, + { + addSuccess: () => false, + editNewExtractionRule: () => true, + }, + ], + fieldRuleFlyoutVisible: [ + false, + { + addExtractionRuleSuccess: () => false, + closeEditRuleFlyout: () => false, + openEditRuleFlyout: () => true, + updateExtractionRuleSuccess: () => false, + }, + ], + fieldRuleToDelete: [ + {}, + { + hideDeleteFieldModal: () => ({}), + showDeleteFieldModal: (_, { extractionRuleId, fieldRuleIndex }) => ({ + extractionRuleId, + fieldRuleIndex, + }), + updateExtractionRuleSuccess: () => ({}), + }, + ], + fieldRuleToEdit: [ + null, + { + closeEditRuleFlyout: () => null, + openEditRuleFlyout: (_, { fieldRule }) => fieldRule ?? null, + }, + ], + fieldRuleToEditIndex: [ + null, + { + closeEditRuleFlyout: () => null, + openEditRuleFlyout: (_, { fieldRuleIndex }) => fieldRuleIndex ?? null, + }, + ], + fieldRuleToEditIsNew: [ + true, + { + closeEditRuleFlyout: () => true, + openEditRuleFlyout: (_, { isNewRule }) => isNewRule, + }, + ], + updatedExtractionRules: [ + null, + { + addExtractionRuleSuccess: (_, { extraction_rules: extractionRules }) => extractionRules, + deleteExtractionRuleSuccess: (_, { extraction_rules: extractionRules }) => extractionRules, + receiveDomainData: () => null, + updateExtractionRuleSuccess: (_, { extraction_rules: extractionRules }) => extractionRules, + }, + ], + }), + selectors: ({ selectors }) => ({ + extractionRules: [ + () => [selectors.domainExtractionRules, selectors.updatedExtractionRules], + ( + domainExtractionRules: ExtractionRule[] | null, + updatedExtractionRules: ExtractionRule[] | null + ) => updatedExtractionRules ?? domainExtractionRules ?? [], + ], + isLoadingUpdate: [ + () => [selectors.updateStatus, selectors.deleteStatus, selectors.addStatus], + (updateStatus: Status, deleteStatus: Status, addStatus: Status) => + [updateStatus, deleteStatus, addStatus].includes(Status.LOADING), + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_table.test.tsx new file mode 100644 index 0000000000000..2cc9bffb0f54f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_table.test.tsx @@ -0,0 +1,302 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockFlashMessageHelpers, setMockActions } from '../../../../../../__mocks__/kea_logic'; + +import React from 'react'; + +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiFieldText, EuiSelect } from '@elastic/eui'; + +import { GenericEndpointInlineEditableTable } from '../../../../../../shared/tables/generic_endpoint_inline_editable_table'; +import { CrawlerPolicies, CrawlerRules } from '../../../../../api/crawler/types'; + +import { CrawlRulesTable, CrawlRulesTableProps } from '../crawl_rules_table'; + +describe('CrawlRulesTable', () => { + const { clearFlashMessages, flashSuccessToast } = mockFlashMessageHelpers; + const indexName = 'index-name'; + const crawlRules = [ + { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, + { id: '2', pattern: '*', policy: CrawlerPolicies.deny, rule: CrawlerRules.endsWith }, + ]; + + const DEFAULT_PROPS: CrawlRulesTableProps = { + crawlRules, + domainId: '6113e1407a2f2e6f42489794', + indexName, + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(GenericEndpointInlineEditableTable).exists()).toBe(true); + }); + + describe('columns', () => { + const crawlRule = { + id: '1', + pattern: '*', + policy: CrawlerPolicies.allow, + rule: CrawlerRules.beginsWith, + }; + let wrapper: ShallowWrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + const renderColumn = (index: number) => { + const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); + return shallow(
{columns[index].render(crawlRule)}
); + }; + + const onChange = jest.fn(); + const renderColumnInEditingMode = (index: number) => { + const columns = wrapper.find(GenericEndpointInlineEditableTable).prop('columns'); + return shallow( +
+ {columns[index].editingRender(crawlRule, onChange, { + isInvalid: false, + isLoading: false, + })} +
+ ); + }; + + describe('policy column', () => { + it('shows the policy of a crawl rule', () => { + expect(renderColumn(0).html()).toContain('Allow'); + }); + + it('can show the policy of a crawl rule as editable', () => { + const column = renderColumnInEditingMode(0); + + const selectField = column.find(EuiSelect); + expect(selectField.props()).toEqual( + expect.objectContaining({ + disabled: false, + isInvalid: false, + options: [ + { text: 'Allow', value: 'allow' }, + { text: 'Disallow', value: 'deny' }, + ], + value: 'allow', + }) + ); + + selectField.simulate('change', { target: { value: 'deny' } }); + expect(onChange).toHaveBeenCalledWith('deny'); + }); + }); + + describe('rule column', () => { + it('shows the rule of a crawl rule', () => { + expect(renderColumn(1).html()).toContain('Begins with'); + }); + + it('can show the rule of a crawl rule as editable', () => { + const column = renderColumnInEditingMode(1); + + const selectField = column.find(EuiSelect); + expect(selectField.props()).toEqual( + expect.objectContaining({ + disabled: false, + isInvalid: false, + options: [ + { text: 'Begins with', value: 'begins' }, + { text: 'Ends with', value: 'ends' }, + { text: 'Contains', value: 'contains' }, + { text: 'Regex', value: 'regex' }, + ], + value: 'begins', + }) + ); + + selectField.simulate('change', { target: { value: 'ends' } }); + expect(onChange).toHaveBeenCalledWith('ends'); + }); + }); + + describe('pattern column', () => { + it('shows the pattern of a crawl rule', () => { + expect(renderColumn(2).html()).toContain('*'); + }); + + it('can show the pattern of a crawl rule as editable', () => { + const column = renderColumnInEditingMode(2); + + const field = column.find(EuiFieldText); + expect(field.props()).toEqual( + expect.objectContaining({ + disabled: false, + isInvalid: false, + value: '*', + }) + ); + + field.simulate('change', { target: { value: 'foo' } }); + expect(onChange).toHaveBeenCalledWith('foo'); + }); + }); + }); + + describe('routes', () => { + it('can calculate an update and delete route correctly', () => { + const wrapper = shallow(); + + const table = wrapper.find(GenericEndpointInlineEditableTable); + + const crawlRule = { + id: '1', + pattern: '*', + policy: CrawlerPolicies.allow, + rule: CrawlerRules.beginsWith, + }; + expect(table.prop('deleteRoute')(crawlRule)).toEqual( + '/internal/enterprise_search/indices/index-name/crawler/domains/6113e1407a2f2e6f42489794/crawl_rules/1' + ); + expect(table.prop('updateRoute')(crawlRule)).toEqual( + '/internal/enterprise_search/indices/index-name/crawler/domains/6113e1407a2f2e6f42489794/crawl_rules/1' + ); + }); + }); + + it('shows a custom description if one is provided', () => { + const wrapper = shallow( + + ); + + const table = wrapper.find(GenericEndpointInlineEditableTable); + expect(table.prop('description')).toEqual('I am a description'); + }); + + it('shows a default crawl rule as uneditable if one is provided', () => { + const wrapper = shallow( + + ); + + const table = wrapper.find(GenericEndpointInlineEditableTable); + expect(table.prop('uneditableItems')).toEqual([crawlRules[0]]); + }); + + describe('when a crawl rule is added', () => { + it('should update the crawl rules for the current domain, and clear flash messages', () => { + const updateCrawlRules = jest.fn(); + setMockActions({ + updateCrawlRules, + }); + const wrapper = shallow( + + ); + const table = wrapper.find(GenericEndpointInlineEditableTable); + + const crawlRulesThatWasAdded = { + id: '2', + pattern: '*', + policy: CrawlerPolicies.deny, + rule: CrawlerRules.endsWith, + }; + const updatedCrawlRules = [ + { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, + { id: '2', pattern: '*', policy: CrawlerPolicies.deny, rule: CrawlerRules.endsWith }, + ]; + table.prop('onAdd')(crawlRulesThatWasAdded, updatedCrawlRules); + expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + + describe('when a crawl rule is updated', () => { + it('should update the crawl rules for the current domain, and clear flash messages', () => { + const updateCrawlRules = jest.fn(); + setMockActions({ + updateCrawlRules, + }); + const wrapper = shallow( + + ); + const table = wrapper.find(GenericEndpointInlineEditableTable); + + const crawlRulesThatWasUpdated = { + id: '2', + pattern: '*', + policy: CrawlerPolicies.deny, + rule: CrawlerRules.endsWith, + }; + const updatedCrawlRules = [ + { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, + { + id: '2', + pattern: 'newPattern', + policy: CrawlerPolicies.deny, + rule: CrawlerRules.endsWith, + }, + ]; + table.prop('onUpdate')(crawlRulesThatWasUpdated, updatedCrawlRules); + expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); + + describe('when a crawl rule is deleted', () => { + it('should update the crawl rules for the current domain, clear flash messages, and show a success', () => { + const updateCrawlRules = jest.fn(); + setMockActions({ + updateCrawlRules, + }); + const wrapper = shallow( + + ); + const table = wrapper.find(GenericEndpointInlineEditableTable); + + const crawlRulesThatWasDeleted = { + id: '2', + pattern: '*', + policy: CrawlerPolicies.deny, + rule: CrawlerRules.endsWith, + }; + const updatedCrawlRules = [ + { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, + ]; + table.prop('onDelete')(crawlRulesThatWasDeleted, updatedCrawlRules); + expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); + expect(clearFlashMessages).toHaveBeenCalled(); + expect(flashSuccessToast).toHaveBeenCalled(); + }); + }); + + describe('when a crawl rule is reordered', () => { + it('should update the crawl rules for the current domain and clear flash messages', () => { + const updateCrawlRules = jest.fn(); + setMockActions({ + updateCrawlRules, + }); + const wrapper = shallow( + + ); + const table = wrapper.find(GenericEndpointInlineEditableTable); + + const updatedCrawlRules = [ + { id: '2', pattern: '*', policy: CrawlerPolicies.deny, rule: CrawlerRules.endsWith }, + { id: '1', pattern: '*', policy: CrawlerPolicies.allow, rule: CrawlerRules.beginsWith }, + ]; + table.prop('onReorder')!(updatedCrawlRules); + expect(updateCrawlRules).toHaveBeenCalledWith(updatedCrawlRules); + expect(clearFlashMessages).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_table.tsx new file mode 100644 index 0000000000000..1a2e545dd4fe4 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/extraction_rules_table.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect, useState } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonEmpty, + EuiCode, + EuiConfirmModal, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedRelative } from '@kbn/i18n-react'; + +import { ExtractionRule } from '../../../../../../../../common/types/extraction_rules'; +import { CANCEL_BUTTON_LABEL } from '../../../../../../shared/constants'; + +import { ContentFieldsPanel } from './content_fields_panel'; +import { ExtractionRulesLogic } from './extraction_rules_logic'; + +export const ExtractionRulesTable: React.FC = () => { + const { + deleteFieldRule, + editExtractionRule, + hideDeleteFieldModal, + openEditRuleFlyout, + showDeleteFieldModal, + showDeleteModal, + } = useActions(ExtractionRulesLogic); + + const { deleteFieldModalVisible, extractionRules } = useValues(ExtractionRulesLogic); + + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState< + Record + >({}); + + useEffect(() => { + setItemIdToExpandedRowMap({}); + }, [extractionRules]); + + const toggleExpandedItem = (item: ExtractionRule) => { + if (itemIdToExpandedRowMap[item.id]) { + // omit item from rowmap + const { [item.id]: _, ...rest } = itemIdToExpandedRowMap; + setItemIdToExpandedRowMap(rest); + } else { + const rules = item.rules.map((val, index) => ({ ...val, id: `${index}`, index })); + const newItem = ( + + { + editExtractionRule(item); + const rule = rules.find(({ id: ruleId }) => id === ruleId); + if (rule) { + openEditRuleFlyout({ + fieldRule: rule, + fieldRuleIndex: rule.index, + isNewRule: false, + }); + } + }} + editNewField={() => { + editExtractionRule(item); + openEditRuleFlyout({ isNewRule: true }); + }} + removeField={(id) => { + const rule = rules.find(({ id: ruleId }) => id === ruleId); + if (rule) { + showDeleteFieldModal({ extractionRuleId: item.id, fieldRuleIndex: rule.index }); + } + }} + /> + + ); + setItemIdToExpandedRowMap({ ...itemIdToExpandedRowMap, [item.id]: newItem }); + } + }; + + const columns: Array> = [ + { + field: 'description', + name: i18n.translate( + 'xpack.enterpriseSearch.crawler.extractionRulesTable.descriptionTableLabel', + { + defaultMessage: 'Description', + } + ), + textOnly: true, + }, + { + field: 'url_filters', + name: i18n.translate('xpack.enterpriseSearch.crawler.extractionRulesTable.urlsLabel', { + defaultMessage: 'URLs', + }), + render: (filters: ExtractionRule['url_filters']) => ( + + {filters.length > 0 ? ( + filters.map(({ pattern }, index) => ( + + {pattern} + + )) + ) : ( + + {'/*'} + + )} + + ), + }, + { + name: i18n.translate('xpack.enterpriseSearch.crawler.extractionRulesTable.rulesLabel', { + defaultMessage: 'Field rules', + }), + render: (rule: ExtractionRule) => ( + toggleExpandedItem(rule)}> + {rule.rules.length} + + ), + textOnly: true, + }, + { + field: 'updated_at', + name: i18n.translate('xpack.enterpriseSearch.crawler.extractionRulesTable.lastUpdatedLabel', { + defaultMessage: 'Last updated', + }), + render: (lastUpdated: string) => , + textOnly: true, + }, + { + field: 'edited_by', + name: i18n.translate('xpack.enterpriseSearch.crawler.extractionRulesTable.editedByLabel', { + defaultMessage: 'Edited by', + }), + render: (editedBy: string) => editedBy, + textOnly: true, + }, + { + actions: [ + { + description: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.editRule.title', + { + defaultMessage: 'Edit this extraction rule', + } + ), + icon: 'pencil', + isPrimary: false, + name: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.editRule.caption', + { + defaultMessage: 'Edit this extraction rule', + } + ), + onClick: (extractionRule) => editExtractionRule(extractionRule), + type: 'icon', + }, + { + color: 'danger', + description: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.deleteRule.title', + { + defaultMessage: 'Delete this extraction rule', + } + ), + icon: 'trash', + isPrimary: false, + name: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.deleteRule.caption', + { + defaultMessage: 'Delete extraction rule', + } + ), + onClick: (extractionRule) => showDeleteModal(extractionRule), + type: 'icon', + }, + { + color: 'primary', + description: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.expandRule.title', + { + defaultMessage: 'Expand this extraction rule', + } + ), + icon: (item) => (!!itemIdToExpandedRowMap[item.id] ? 'arrowUp' : 'arrowDown'), + isPrimary: true, + name: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.expandRule.caption', + { + defaultMessage: 'Expand rule', + } + ), + onClick: (extractionRule) => toggleExpandedItem(extractionRule), + type: 'icon', + }, + ], + name: i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.actions.label', { + defaultMessage: 'Actions', + }), + }, + ]; + + return ( + <> + {deleteFieldModalVisible && ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.deleteFieldModal.description', + { + defaultMessage: 'This action cannot be undone.', + } + )} + + )} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/field_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/field_rules_table.tsx new file mode 100644 index 0000000000000..c8f6b3a882a96 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/extraction_rules/field_rules_table.tsx @@ -0,0 +1,151 @@ +/* + * 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 { EuiBasicTable, EuiBasicTableColumn, EuiCode, EuiFlexGroup, EuiText } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { + ContentFrom, + ExtractionRuleFieldRule, + FieldType, + MultipleObjectsHandling, +} from '../../../../../../../../common/types/extraction_rules'; + +type FieldRuleWithId = ExtractionRuleFieldRule & { id: string }; + +export interface FieldRulesTableProps { + editRule: (id: string) => void; + fieldRules: FieldRuleWithId[]; + removeRule: (id: string) => void; +} + +export const FieldRulesTable: React.FC = ({ + editRule, + fieldRules, + removeRule, +}) => { + const columns: Array> = [ + { + field: 'field_name', + name: i18n.translate( + 'xpack.enterpriseSearch.crawler.extractionRules.fieldRulesTable.fieldNameLabel', + { + defaultMessage: 'Field name', + } + ), + textOnly: true, + }, + { + name: i18n.translate('xpack.enterpriseSearch.crawler.extractionRulesTable.sourceLabel', { + defaultMessage: 'Source', + }), + render: (rule: FieldRuleWithId) => ( + + + {rule.source_type === FieldType.HTML + ? i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.HTMLLabel', { + defaultMessage: 'HTML: ', + }) + : i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.UrlLabel', { + defaultMessage: 'URL: ', + })} + + {rule.selector} + + ), + }, + { + name: i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.contentLabel', { + defaultMessage: 'Content', + }), + render: ({ + content_from: content, + multiple_objects_handling: multipleObjectsHandling, + }: FieldRuleWithId) => ( + + + {content.value_type === ContentFrom.EXTRACTED + ? i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.extractedLabel', { + defaultMessage: 'Extracted as: ', + }) + : i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.fixedLabel', { + defaultMessage: 'Fixed value: ', + })} + + + {content.value_type === ContentFrom.FIXED + ? content.value + : multipleObjectsHandling === MultipleObjectsHandling.ARRAY + ? i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.arrayLabel', { + defaultMessage: 'array', + }) + : i18n.translate('xpack.enterpriseSearch.crawler.fieldRulesTable.stringLabel', { + defaultMessage: 'string', + })} + + + ), + }, + { + actions: [ + { + description: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.fieldRulesTable.editRule.title', + { + defaultMessage: 'Edit this content field rule', + } + ), + icon: 'pencil', + isPrimary: false, + name: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.fieldRulesTable.editRule.caption', + { + defaultMessage: 'Edit this content field rule', + } + ), + onClick: ({ id }) => editRule(id), + type: 'icon', + }, + { + color: 'danger', + description: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.deleteRule.title', + { + defaultMessage: 'Delete this extraction rule', + } + ), + icon: 'trash', + isPrimary: false, + name: i18n.translate( + 'xpack.enterpriseSearch.content.crawler.extractionRules.actions.deleteRule.caption', + { + defaultMessage: 'Delete extraction rule', + } + ), + onClick: ({ id }) => removeRule(id), + type: 'icon', + }, + ], + name: i18n.translate('xpack.enterpriseSearch.content.crawler.extractionRules.actions.label', { + defaultMessage: 'Actions', + }), + }, + ]; + + return ( + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.test.tsx index 1cae5f5cbe096..e556c32b6eded 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.test.tsx @@ -35,6 +35,7 @@ describe('SitemapsTable', () => { url: 'https://www.elastic.co', crawlRules: [], entryPoints: [], + extractionRules: [], sitemaps, deduplicationEnabled: true, deduplicationFields: ['title'], diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.tsx index c486cb2eeaed7..80ae2e886b095 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawler_domain_detail/sitemaps_table.tsx @@ -68,7 +68,8 @@ export const SitemapsTable: React.FC = ({ domain, indexName, description={

{i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.description', { - defaultMessage: 'Specify sitemap URLs for the crawler on this domain.', + defaultMessage: + 'Add custom sitemap URLs for this domain. The crawler automatically detects existing sitemaps.', })}

} @@ -112,9 +113,7 @@ export const SitemapsTable: React.FC = ({ domain, indexName, updateSitemaps(newSitemaps as Sitemap[]); clearFlashMessages(); }} - title={i18n.translate('xpack.enterpriseSearch.crawler.sitemapsTable.title', { - defaultMessage: 'Sitemaps', - })} + title="" disableReordering /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.test.tsx index 350ef6ba68672..37250d99e789a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/domain_management/domains_table.test.tsx @@ -37,6 +37,7 @@ const domains: CrawlerDomain[] = [ createdOn: '2020-01-01T00:00:00-12:00', deduplicationEnabled: false, deduplicationFields: ['title'], + extractionRules: [], availableDeduplicationFields: ['title', 'description'], auth: null, }, @@ -46,6 +47,7 @@ const domains: CrawlerDomain[] = [ url: 'empty.site', crawlRules: [], entryPoints: [], + extractionRules: [], sitemaps: [], createdOn: '1970-01-01T00:00:00-12:00', deduplicationEnabled: false, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx index 822add8f8bde3..c885160693f44 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs.tsx @@ -16,6 +16,7 @@ import { i18n } from '@kbn/i18n'; import { SyncStatus } from '../../../../../../common/types/connectors'; import { FormattedDateTime } from '../../../../shared/formatted_date_time'; +import { pageToPagination } from '../../../../shared/pagination/page_to_pagination'; import { durationToText } from '../../../utils/duration_to_text'; import { syncStatusToColor, syncStatusToText } from '../../../utils/sync_status_to_text'; @@ -35,8 +36,8 @@ export const SyncJobs: React.FC = () => { if (connectorId) { fetchSyncJobs({ connectorId, - page: syncJobsPagination.pageIndex ?? 0, - size: syncJobsPagination.pageSize ?? 10, + from: syncJobsPagination.from ?? 0, + size: syncJobsPagination.size ?? 10, }); } }, [connectorId]); @@ -119,13 +120,10 @@ export const SyncJobs: React.FC = () => { hasActions onChange={({ page: { index, size } }: { page: { index: number; size: number } }) => { if (connectorId) { - fetchSyncJobs({ connectorId, page: index, size }); + fetchSyncJobs({ connectorId, from: index * size, size }); } }} - pagination={{ - ...syncJobsPagination, - totalItemCount: syncJobsPagination.total, - }} + pagination={pageToPagination(syncJobsPagination)} tableLayout="fixed" loading={syncJobsLoading} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts index 28af9f3d1a72d..1b965632194b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.test.ts @@ -32,11 +32,9 @@ const DEFAULT_VALUES = { syncJobsData: undefined, syncJobsLoading: true, syncJobsPagination: { - data: [], + from: 0, has_more_hits_than_total: false, - pageIndex: 0, - pageSize: 10, - size: 0, + size: 10, total: 0, }, syncJobsStatus: Status.IDLE, @@ -93,30 +91,35 @@ describe('SyncJobsViewLogic', () => { }; it('should update values', async () => { FetchSyncJobsApiLogic.actions.apiSuccess({ + _meta: { + page: { + from: 40, + has_more_hits_than_total: false, + size: 20, + total: 50, + }, + }, data: [syncJob], - has_more_hits_than_total: false, - pageIndex: 3, - pageSize: 20, - size: 20, - total: 50, }); await nextTick(); expect(SyncJobsViewLogic.values).toEqual({ ...DEFAULT_VALUES, syncJobs: [syncJobView], syncJobsData: { + _meta: { + page: { + from: 40, + has_more_hits_than_total: false, + size: 20, + total: 50, + }, + }, data: [syncJob], - has_more_hits_than_total: false, - pageIndex: 3, - pageSize: 20, - size: 20, - total: 50, }, syncJobsLoading: false, syncJobsPagination: { + from: 40, has_more_hits_than_total: false, - pageIndex: 3, - pageSize: 20, size: 20, total: 50, }, @@ -125,6 +128,14 @@ describe('SyncJobsViewLogic', () => { }); it('should update values for incomplete job', async () => { FetchSyncJobsApiLogic.actions.apiSuccess({ + _meta: { + page: { + from: 40, + has_more_hits_than_total: false, + size: 20, + total: 50, + }, + }, data: [ { ...syncJob, @@ -133,11 +144,6 @@ describe('SyncJobsViewLogic', () => { status: SyncStatus.IN_PROGRESS, }, ], - has_more_hits_than_total: false, - pageIndex: 3, - pageSize: 20, - size: 20, - total: 50, }); await nextTick(); expect(SyncJobsViewLogic.values).toEqual({ @@ -153,6 +159,14 @@ describe('SyncJobsViewLogic', () => { }, ], syncJobsData: { + _meta: { + page: { + from: 40, + has_more_hits_than_total: false, + size: 20, + total: 50, + }, + }, data: [ { ...syncJob, @@ -161,17 +175,11 @@ describe('SyncJobsViewLogic', () => { status: SyncStatus.IN_PROGRESS, }, ], - has_more_hits_than_total: false, - pageIndex: 3, - pageSize: 20, - size: 20, - total: 50, }, syncJobsLoading: false, syncJobsPagination: { + from: 40, has_more_hits_than_total: false, - pageIndex: 3, - pageSize: 20, size: 20, total: 50, }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.ts index e8feb159dbcd0..cd17eb9a931d6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/sync_jobs/sync_jobs_view_logic.ts @@ -12,7 +12,7 @@ import moment from 'moment'; import { Status } from '../../../../../../common/types/api'; import { ConnectorSyncJob } from '../../../../../../common/types/connectors'; -import { Paginate } from '../../../../../../common/types/pagination'; +import { Page, Paginate } from '../../../../../../common/types/pagination'; import { Actions } from '../../../../shared/api_logic/create_api_logic'; import { FetchSyncJobsApiLogic, @@ -37,7 +37,7 @@ export interface IndexViewValues { syncJobs: SyncJobView[]; syncJobsData: Paginate | null; syncJobsLoading: boolean; - syncJobsPagination: Paginate; + syncJobsPagination: Page; syncJobsStatus: Status; } @@ -85,13 +85,11 @@ export const SyncJobsViewLogic = kea [selectors.syncJobsData], (data?: Paginate) => data - ? { ...data, data: undefined } + ? data._meta.page : { - data: [], + from: 0, has_more_hits_than_total: false, - pageIndex: 0, - pageSize: 10, - size: 0, + size: 10, total: 0, }, ], diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx index 1d6de5d5b3b93..2b25fabe056f0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx @@ -45,6 +45,7 @@ export const renderApp = ( const noProductAccess: ProductAccess = { hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }; const productAccess = data.access || noProductAccess; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx index 103d24ebbe615..17ce1fce0512f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx @@ -15,8 +15,6 @@ import { EuiSideNavItemType } from '@elastic/eui'; import { ProductAccess } from '../../../../common/types'; -import { enableEnginesSection } from '../../../../common/ui_settings_keys'; - import { useEnterpriseSearchNav, useEnterpriseSearchEngineNav } from './nav'; describe('useEnterpriseSearchContentNav', () => { @@ -28,6 +26,7 @@ describe('useEnterpriseSearchContentNav', () => { it('returns an array of top-level Enterprise Search nav items', () => { const fullProductAccess: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }; setMockValues({ productAccess: fullProductAccess }); @@ -93,12 +92,12 @@ describe('useEnterpriseSearchContentNav', () => { name: 'Search', }, ]); - expect(mockKibanaValues.uiSettings.get).toHaveBeenCalledWith(enableEnginesSection, false); }); it('excludes legacy products when the user has no access to them', () => { const noProductAccess: ProductAccess = { hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }; @@ -129,6 +128,7 @@ describe('useEnterpriseSearchContentNav', () => { it('excludes App Search when the user has no access to it', () => { const workplaceSearchProductAccess: ProductAccess = { hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }; @@ -163,6 +163,7 @@ describe('useEnterpriseSearchContentNav', () => { it('excludes Workplace Search when the user has no access to it', () => { const appSearchProductAccess: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }; @@ -197,6 +198,7 @@ describe('useEnterpriseSearchContentNav', () => { it('excludes engines when feature flag is off', () => { const fullProductAccess: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }; setMockValues({ productAccess: fullProductAccess }); @@ -209,12 +211,12 @@ describe('useEnterpriseSearchContentNav', () => { describe('useEnterpriseSearchContentNav Engines feature flag', () => { beforeEach(() => { jest.clearAllMocks(); - mockKibanaValues.uiSettings.get.mockReturnValue(true); }); it('returns an array of top-level Enterprise Search nav items', () => { const fullProductAccess: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: true, hasWorkplaceSearchAccess: true, }; setMockValues({ productAccess: fullProductAccess }); @@ -286,12 +288,12 @@ describe('useEnterpriseSearchContentNav Engines feature flag', () => { name: 'Standalone Experiences', }, ]); - expect(mockKibanaValues.uiSettings.get).toHaveBeenCalledWith(enableEnginesSection, false); }); it('excludes standalone experiences when the user has no access to them', () => { const fullProductAccess: ProductAccess = { hasAppSearchAccess: false, + hasSearchEnginesAccess: true, hasWorkplaceSearchAccess: false, }; setMockValues({ productAccess: fullProductAccess }); @@ -302,6 +304,7 @@ describe('useEnterpriseSearchContentNav Engines feature flag', () => { it('excludes App Search when the user has no access to it', () => { const fullProductAccess: ProductAccess = { hasAppSearchAccess: false, + hasSearchEnginesAccess: true, hasWorkplaceSearchAccess: true, }; setMockValues({ productAccess: fullProductAccess }); @@ -324,6 +327,7 @@ describe('useEnterpriseSearchContentNav Engines feature flag', () => { it('excludes Workplace Search when the user has no access to it', () => { const fullProductAccess: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: true, hasWorkplaceSearchAccess: false, }; setMockValues({ productAccess: fullProductAccess }); @@ -351,6 +355,7 @@ describe('useEnterpriseSearchEngineNav', () => { mockKibanaValues.uiSettings.get.mockReturnValue(true); const fullProductAccess: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: true, hasWorkplaceSearchAccess: true, }; setMockValues({ productAccess: fullProductAccess }); @@ -424,7 +429,6 @@ describe('useEnterpriseSearchEngineNav', () => { name: 'Standalone Experiences', }, ]); - expect(mockKibanaValues.uiSettings.get).toHaveBeenCalledWith(enableEnginesSection, false); }); it('returns selected engine sub nav items', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx index 8ffb822352324..a6e550e7c58b2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx @@ -19,7 +19,6 @@ import { SEARCH_EXPERIENCES_PLUGIN, WORKPLACE_SEARCH_PLUGIN, } from '../../../../common/constants'; -import { enableEnginesSection } from '../../../../common/ui_settings_keys'; import { ENGINES_PATH, SEARCH_INDICES_PATH, @@ -31,9 +30,9 @@ import { KibanaLogic } from '../kibana'; import { generateNavLink } from './nav_link_helpers'; export const useEnterpriseSearchNav = () => { - const { productAccess, uiSettings } = useValues(KibanaLogic); + const { productAccess } = useValues(KibanaLogic); - const enginesSectionEnabled = uiSettings?.get(enableEnginesSection, false); + const enginesSectionEnabled = productAccess.hasSearchEnginesAccess; const navItems: Array> = [ { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/pagination/page_to_pagination.ts b/x-pack/plugins/enterprise_search/public/applications/shared/pagination/page_to_pagination.ts new file mode 100644 index 0000000000000..4283ea4c3e6b9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/pagination/page_to_pagination.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Page } from '../../../../common/types/pagination'; + +export function pageToPagination(page: Page) { + // Prevent divide-by-zero-error + const pageIndex = page.size ? Math.trunc(page.from / page.size) : 0; + return { + pageIndex, + pageSize: page.size, + totalItemCount: page.total, + }; +} diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts index e312bada49e41..6ba4d9484ea87 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts @@ -38,6 +38,9 @@ describe('Setup Indices', () => { configuration: { type: 'object', }, + custom_scheduling: { + type: 'object', + }, description: { type: 'text' }, error: { type: 'keyword' }, features: { diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts index 36757667193a3..10ff75fcb566d 100644 --- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts +++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts @@ -30,6 +30,7 @@ interface IndexDefinition { const connectorMappingsProperties: Record = { api_key_id: { type: 'keyword' }, configuration: { type: 'object' }, + custom_scheduling: { type: 'object' }, description: { type: 'text' }, error: { type: 'keyword' }, features: { diff --git a/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts b/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts index 3da63a63828ae..d2be503dbdebd 100644 --- a/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/check_access.test.ts @@ -59,6 +59,7 @@ describe('checkAccess', () => { }; expect(await checkAccess({ ...mockDependencies, security })).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -71,6 +72,7 @@ describe('checkAccess', () => { }; expect(await checkAccess({ ...mockDependencies, request })).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -81,6 +83,7 @@ describe('checkAccess', () => { mockSpaces.spacesService.getActiveSpace.mockResolvedValueOnce(disabledSpace); expect(await checkAccess({ ...mockDependencies })).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -94,6 +97,7 @@ describe('checkAccess', () => { ); expect(await checkAccess({ ...mockDependencies })).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -134,6 +138,7 @@ describe('checkAccess', () => { }; expect(await checkAccess({ ...mockDependencies, security })).toEqual({ hasAppSearchAccess: true, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }); }); @@ -149,6 +154,7 @@ describe('checkAccess', () => { }; expect(await checkAccess({ ...mockDependencies, security })).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -170,6 +176,7 @@ describe('checkAccess', () => { const config = { host: undefined }; expect(await checkAccess({ ...mockDependencies, config })).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -180,11 +187,13 @@ describe('checkAccess', () => { (callEnterpriseSearchConfigAPI as jest.Mock).mockImplementationOnce(() => ({ access: { hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }, })); expect(await checkAccess(mockDependencies)).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: true, }); }); @@ -193,6 +202,7 @@ describe('checkAccess', () => { (callEnterpriseSearchConfigAPI as jest.Mock).mockImplementationOnce(() => ({})); expect(await checkAccess(mockDependencies)).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); @@ -204,6 +214,7 @@ describe('checkAccess', () => { })); expect(await checkAccess(mockDependencies)).toEqual({ hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/check_access.ts b/x-pack/plugins/enterprise_search/server/lib/check_access.ts index 444fa9d4fdb29..1f351d05785ad 100644 --- a/x-pack/plugins/enterprise_search/server/lib/check_access.ts +++ b/x-pack/plugins/enterprise_search/server/lib/check_access.ts @@ -23,12 +23,14 @@ interface CheckAccess { log: Logger; } -const ALLOW_ALL_PLUGINS = { +const ALLOW_ALL_PLUGINS: ProductAccess = { hasAppSearchAccess: true, + hasSearchEnginesAccess: false, // still false unless Feature Flag explicitly enabled on backend hasWorkplaceSearchAccess: true, }; -const DENY_ALL_PLUGINS = { +const DENY_ALL_PLUGINS: ProductAccess = { hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }; diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts index 14c3532efee91..e6584c0a8b205 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts @@ -85,6 +85,7 @@ describe('addConnector lib function', () => { document: { api_key_id: null, configuration: {}, + custom_scheduling: {}, description: null, error: null, features: null, @@ -269,6 +270,7 @@ describe('addConnector lib function', () => { document: { api_key_id: null, configuration: {}, + custom_scheduling: {}, description: null, error: null, features: null, @@ -375,6 +377,7 @@ describe('addConnector lib function', () => { document: { api_key_id: null, configuration: {}, + custom_scheduling: {}, description: null, error: null, features: null, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts index e697cf57522f8..507ec3fd0bb4f 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts @@ -93,6 +93,7 @@ export const addConnector = async ( const document: ConnectorDocument = { api_key_id: null, configuration: {}, + custom_scheduling: {}, description: null, error: null, features: null, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts index 6766f0c7535d8..5408ac4d4a9e7 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.test.ts @@ -31,12 +31,15 @@ describe('fetchSyncJobs lib', () => { Promise.resolve({ hits: { hits: ['result1', 'result2'] }, total: 2 }) ); await expect(fetchSyncJobsByConnectorId(mockClient as any, 'id', 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, data: [], - has_more_hits_than_total: false, - pageIndex: 0, - pageSize: 10, - size: 0, - total: 0, }); expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({ from: 0, @@ -56,12 +59,15 @@ describe('fetchSyncJobs lib', () => { }); it('should return empty result if size is 0', async () => { await expect(fetchSyncJobsByConnectorId(mockClient as any, 'id', 0, 0)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, data: [], - has_more_hits_than_total: false, - pageIndex: 0, - pageSize: 0, - size: 0, - total: 0, }); expect(mockClient.asCurrentUser.search).not.toHaveBeenCalled(); }); @@ -78,12 +84,15 @@ describe('fetchSyncJobs lib', () => { }) ); await expect(fetchSyncJobsByConnectorId(mockClient as any, 'id', 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, data: [], - has_more_hits_than_total: false, - pageIndex: 0, - pageSize: 10, - size: 0, - total: 0, }); expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({ from: 0, @@ -114,13 +123,14 @@ describe('fetchSyncJobs lib', () => { }, }) ); - await expect(fetchSyncJobsByConnectorId(mockClient as any, 'id', 0, 10)).resolves.toEqual({ - data: [], - has_more_hits_than_total: false, - pageIndex: 0, - pageSize: 10, - size: 0, - total: 0, + await expect(fetchSyncJobsByConnectorId(mockClient as any, 'id', 0, 10)).rejects.toEqual({ + meta: { + body: { + error: { + type: 'other error', + }, + }, + }, }); expect(mockClient.asCurrentUser.search).toHaveBeenCalledWith({ from: 0, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts index a4e2fe119eed5..25369fa7979bb 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/fetch_sync_jobs.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { CONNECTORS_JOBS_INDEX } from '../..'; @@ -14,75 +13,56 @@ import { Paginate } from '../../../common/types/pagination'; import { isNotNullish } from '../../../common/utils/is_not_nullish'; import { setupConnectorsIndices } from '../../index_management/setup_indices'; +import { fetchWithPagination } from '../../utils/fetch_with_pagination'; import { isIndexNotFoundException } from '../../utils/identify_exceptions'; +const defaultResult: Paginate = { + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data: [], +}; + export const fetchSyncJobsByConnectorId = async ( client: IScopedClusterClient, connectorId: string, - pageIndex: number, + from: number, size: number ): Promise> => { try { - if (size === 0) { - // prevent some divide by zero errors below - return { - data: [], - has_more_hits_than_total: false, - pageIndex: 0, - pageSize: size, - size: 0, - total: 0, - }; - } - const result = await client.asCurrentUser.search({ - from: pageIndex * size, - index: CONNECTORS_JOBS_INDEX, - query: { - term: { - 'connector.id': connectorId, - }, - }, - size, - // @ts-ignore Elasticsearch-js has the wrong internal typing for this field - sort: { created_at: { order: 'desc' } }, - }); - const total = totalToPaginateTotal(result.hits.total); - // If we get fewer results than the target page, make sure we return correct page we're on - const resultPageIndex = Math.min(pageIndex, Math.trunc(total.total / size)); - const data = - result.hits.hits - .map((hit) => (hit._source ? { ...hit._source, id: hit._id } : null)) - .filter(isNotNullish) ?? []; + const result = await fetchWithPagination( + async () => + await client.asCurrentUser.search({ + from, + index: CONNECTORS_JOBS_INDEX, + query: { + term: { + 'connector.id': connectorId, + }, + }, + size, + sort: { created_at: { order: 'desc' } }, + }), + from, + size + ); return { - data, - pageIndex: resultPageIndex, - pageSize: size, - size: data.length, - ...total, + ...result, + data: result.data + .map((hit) => (hit._source ? { ...hit._source, id: hit._id } : null)) + .filter(isNotNullish), }; } catch (error) { if (isIndexNotFoundException(error)) { await setupConnectorsIndices(client.asCurrentUser); + return defaultResult; + } else { + throw error; } - return { - data: [], - has_more_hits_than_total: false, - pageIndex: 0, - pageSize: size, - size: 0, - total: 0, - }; } }; - -function totalToPaginateTotal(input: number | SearchTotalHits | undefined): { - has_more_hits_than_total: boolean; - total: number; -} { - if (typeof input === 'number') { - return { has_more_hits_than_total: false, total: input }; - } - return input - ? { has_more_hits_than_total: input.relation === 'gte' ? true : false, total: input.value } - : { has_more_hits_than_total: false, total: 0 }; -} diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts index 144a9b1dbc8b3..25956b271245a 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts @@ -35,6 +35,7 @@ describe('addConnector lib function', () => { api_key_id: null, configuration: {}, created_at: null, + custom_scheduling: {}, error: null, index_name: 'index_name', last_seen: null, @@ -59,6 +60,7 @@ describe('addConnector lib function', () => { api_key_id: null, configuration: {}, created_at: null, + custom_scheduling: {}, error: null, index_name: 'index_name', last_seen: null, diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts index 68d3f7b17ef58..2fa4c0954b245 100644 --- a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts @@ -34,6 +34,7 @@ describe('addConnector lib function', () => { api_key_id: null, configuration: {}, created_at: null, + custom_scheduling: {}, error: null, index_name: 'index_name', last_seen: null, @@ -61,6 +62,7 @@ describe('addConnector lib function', () => { api_key_id: null, configuration: {}, created_at: null, + custom_scheduling: {}, error: null, index_name: 'index_name', last_seen: null, diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts index 5826fbca88b5c..41031eaf06678 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.test.ts @@ -70,6 +70,7 @@ describe('callEnterpriseSearchConfigAPI', () => { name: 'someuser', access: { app_search: true, + search_engines: true, workplace_search: false, }, app_search: { @@ -123,6 +124,7 @@ describe('callEnterpriseSearchConfigAPI', () => { kibanaVersion: '1.0.0', access: { hasAppSearchAccess: true, + hasSearchEnginesAccess: true, hasWorkplaceSearchAccess: false, }, publicUrl: 'http://some.vanity.url', @@ -136,6 +138,7 @@ describe('callEnterpriseSearchConfigAPI', () => { kibanaVersion: '1.0.0', access: { hasAppSearchAccess: false, + hasSearchEnginesAccess: false, hasWorkplaceSearchAccess: false, }, publicUrl: undefined, diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts index 3b9a37b32d1d7..b9e2ebdebfafa 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_config_api.ts @@ -87,6 +87,7 @@ export const callEnterpriseSearchConfigAPI = async ({ kibanaVersion: kibanaPackageJson.version, access: { hasAppSearchAccess: !!data?.current_user?.access?.app_search, + hasSearchEnginesAccess: !!data?.current_user?.access?.search_engines, hasWorkplaceSearchAccess: !!data?.current_user?.access?.workplace_search, }, publicUrl: stripTrailingSlash(data?.settings?.external_url), diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 602d8c48d520e..c1ce03f33b587 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -6,6 +6,7 @@ */ import { RouteDependencies } from '../../plugin'; +import { registerCrawlerExtractionRulesRoutes } from '../enterprise_search/crawler/crawler_extraction_rules'; import { registerSearchRelevanceSuggestionsRoutes } from './adaptive_relevance'; import { registerAnalyticsRoutes } from './analytics'; @@ -50,6 +51,7 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerCrawlerRoutes(dependencies); registerCrawlerEntryPointRoutes(dependencies); registerCrawlerCrawlRulesRoutes(dependencies); + registerCrawlerExtractionRulesRoutes(dependencies); registerCrawlerSitemapRoutes(dependencies); registerSearchRelevanceSuggestionsRoutes(dependencies); }; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler_extraction_rules.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler_extraction_rules.ts new file mode 100644 index 0000000000000..a5b9d38e70d9d --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/crawler/crawler_extraction_rules.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../../plugin'; + +const extractionRuleSchema = schema.object({ + extraction_rule: schema.object({ + description: schema.string(), + rules: schema.arrayOf( + schema.object({ + content_from: schema.object({ + value: schema.nullable(schema.string()), + value_type: schema.string(), + }), + field_name: schema.string(), + multiple_objects_handling: schema.string(), + selector: schema.string(), + source_type: schema.string(), + }) + ), + url_filters: schema.arrayOf( + schema.object({ filter: schema.string(), pattern: schema.string() }) + ), + }), +}); + +export function registerCrawlerExtractionRulesRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.post( + { + path: '/internal/enterprise_search/indices/{indexName}/crawler/domains/{domainId}/extraction_rules', + validate: { + body: extractionRuleSchema, + params: schema.object({ + domainId: schema.string(), + indexName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + params: { + respond_with: 'index', + }, + path: '/api/ent/v1/internal/indices/:indexName/crawler2/domains/:domainId/extraction_rules', + }) + ); + + router.put( + { + path: '/internal/enterprise_search/indices/{indexName}/crawler/domains/{domainId}/extraction_rules/{crawlRuleId}', + validate: { + body: extractionRuleSchema, + params: schema.object({ + crawlRuleId: schema.string(), + domainId: schema.string(), + indexName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + params: { + respond_with: 'index', + }, + path: '/api/ent/v1/internal/indices/:indexName/crawler2/domains/:domainId/extraction_rules/:crawlRuleId', + }) + ); + + router.delete( + { + path: '/internal/enterprise_search/indices/{indexName}/crawler/domains/{domainId}/extraction_rules/{crawlRuleId}', + validate: { + params: schema.object({ + crawlRuleId: schema.string(), + domainId: schema.string(), + indexName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + params: { + respond_with: 'index', + }, + path: '/api/ent/v1/internal/indices/:indexName/crawler2/domains/:domainId/extraction_rules/:crawlRuleId', + }) + ); + + router.get( + { + path: '/internal/enterprise_search/indices/{indexName}/crawler/domains/{domainId}/extraction_rules/{crawlRuleId}', + validate: { + params: schema.object({ + crawlRuleId: schema.string(), + domainId: schema.string(), + indexName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + params: { + respond_with: 'index', + }, + path: '/api/ent/v1/internal/indices/:indexName/crawler2/domains/:domainId/extraction_rules/:crawlRuleId', + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/ui_settings.ts b/x-pack/plugins/enterprise_search/server/ui_settings.ts index c49d1287b1bf6..99fb5906a3050 100644 --- a/x-pack/plugins/enterprise_search/server/ui_settings.ts +++ b/x-pack/plugins/enterprise_search/server/ui_settings.ts @@ -5,25 +5,9 @@ * 2.0. */ -import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from '@kbn/core/types'; -import { i18n } from '@kbn/i18n'; -import { enterpriseSearchFeatureId, enableEnginesSection } from '../common/ui_settings_keys'; /** * uiSettings definitions for Enterprise Search */ -export const uiSettings: Record> = { - [enableEnginesSection]: { - category: [enterpriseSearchFeatureId], - description: i18n.translate('xpack.enterpriseSearch.uiSettings.engines.description', { - defaultMessage: 'Enable the new Engines section in Enterprise Search.', - }), - name: i18n.translate('xpack.enterpriseSearch.uiSettings.engines.name', { - defaultMessage: 'Enable Engines', - }), - requiresPageReload: false, - schema: schema.boolean(), - value: false, - }, -}; +export const uiSettings: Record> = {}; diff --git a/x-pack/plugins/enterprise_search/server/utils/fetch_with_pagination.test.ts b/x-pack/plugins/enterprise_search/server/utils/fetch_with_pagination.test.ts new file mode 100644 index 0000000000000..82b2757abd394 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/fetch_with_pagination.test.ts @@ -0,0 +1,124 @@ +/* + * 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 { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { fetchWithPagination } from './fetch_with_pagination'; + +describe('fetchWithPagination util', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('fetchWithPagination', () => { + it('should fetch mock data with pagination', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { hits: ['result1', 'result2'], total: 2 }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 2, + }, + }, + data: ['result1', 'result2'], + }); + }); + it('should return empty result if size is 0', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { hits: [], total: 0 }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 0)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data: [], + }); + }); + it('should handle total as an object correctly', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { + hits: [], + total: { + relation: 'lte', + value: 555, + }, + }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 555, + }, + }, + data: [], + }); + }); + + it('should handle undefined total correctly', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { + hits: [], + total: undefined, + }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 0, 10)).resolves.toEqual({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data: [], + }); + }); + + it('should handle has_more_hits_than_total correctly', async () => { + const mockFn = jest.fn(); + mockFn.mockImplementation(() => + Promise.resolve({ + hits: { hits: ['result1', 'result2'], total: { relation: 'gte', value: 10000 } }, + } as any as SearchResponse) + ); + await expect(fetchWithPagination(mockFn, 50, 10)).resolves.toEqual({ + _meta: { + page: { + from: 50, + has_more_hits_than_total: true, + size: 10, + total: 10000, + }, + }, + data: ['result1', 'result2'], + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/utils/fetch_with_pagination.ts b/x-pack/plugins/enterprise_search/server/utils/fetch_with_pagination.ts new file mode 100644 index 0000000000000..be6a3db6a61d9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/utils/fetch_with_pagination.ts @@ -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 { SearchHit, SearchResponse, SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; + +import { Paginate } from '../../common/types/pagination'; + +const defaultResult = (data: T[]) => ({ + _meta: { + page: { + from: 0, + has_more_hits_than_total: false, + size: 10, + total: 0, + }, + }, + data, +}); + +export const fetchWithPagination = async ( + fetchFunction: () => Promise>, + from: number, + size: number +): Promise>> => { + if (size === 0) { + return defaultResult>([]); + } + const result = await fetchFunction(); + const total = totalToPaginateTotal(result.hits.total); + return { + _meta: { + page: { + from, + size, + ...total, + }, + }, + data: result.hits.hits, + }; +}; + +function totalToPaginateTotal(input: number | SearchTotalHits | undefined): { + has_more_hits_than_total: boolean; + total: number; +} { + if (typeof input === 'number') { + return { has_more_hits_than_total: false, total: input }; + } + + return input + ? { + has_more_hits_than_total: input.relation === 'gte' ? true : false, + total: input.value, + } + : { has_more_hits_than_total: false, total: 0 }; +} diff --git a/x-pack/plugins/file_upload/public/components/__snapshots__/import_complete_view.test.tsx.snap b/x-pack/plugins/file_upload/public/components/__snapshots__/import_complete_view.test.tsx.snap new file mode 100644 index 0000000000000..d24f877c21cb6 --- /dev/null +++ b/x-pack/plugins/file_upload/public/components/__snapshots__/import_complete_view.test.tsx.snap @@ -0,0 +1,519 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render error when upload fails from elasticsearch request failure 1`] = ` + + +

+ Error: simulated elasticsearch request failure +

+
+ + + +

+ Import response +

+
+
+ + + + + +
+
+ +
+ +
+`; + +exports[`Should render error when upload fails from http request timeout 1`] = ` + + +

+ Error: simulated http request timeout +

+
+ + + +

+ Import response +

+
+
+ + + + + +
+
+ +
+ +
+`; + +exports[`Should render success 1`] = ` + + +

+ Indexed 10 features. +

+
+ + + +

+ Import response +

+
+
+ + + + + +
+
+ +
+ + + + +

+ Data view response +

+
+
+ + + + + +
+
+ +
+ + +

+ + + + +

+
+
+`; + +exports[`Should render warning when some features failed import 1`] = ` + + +

+ Unable to index 1 of 10 features. +

+
+ + + +

+ Import response +

+
+
+ + + + + +
+
+ +
+ + + + +

+ Data view response +

+
+
+ + + + + +
+
+ +
+ + +

+ + + + +

+
+
+`; diff --git a/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx b/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx index 0c7f09c56f36f..b3d1711b1d2a8 100644 --- a/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx +++ b/x-pack/plugins/file_upload/public/components/geo_upload_wizard.tsx @@ -17,6 +17,7 @@ import { ImportResults } from '../importer'; import { GeoFileImporter } from '../importer/geo'; import type { Settings } from '../../common/types'; import { hasImportPermission } from '../api'; +import { getPartialImportMessage } from './utils'; enum PHASE { CONFIGURE = 'CONFIGURE', @@ -175,6 +176,25 @@ export class GeoUploadWizard extends Component }); this.props.onUploadError(); return; + } else if (importResults.docCount === importResults.failures?.length) { + this.setState({ + // Force importResults into failure shape when no features are indexed + importResults: { + ...importResults, + success: false, + error: { + error: { + reason: getPartialImportMessage( + importResults.failures!.length, + importResults.docCount + ), + }, + }, + }, + phase: PHASE.COMPLETE, + }); + this.props.onUploadError(); + return; } // diff --git a/x-pack/plugins/file_upload/public/components/import_complete_view.test.tsx b/x-pack/plugins/file_upload/public/components/import_complete_view.test.tsx new file mode 100644 index 0000000000000..a31d8dcd04b84 --- /dev/null +++ b/x-pack/plugins/file_upload/public/components/import_complete_view.test.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 from 'react'; +import { shallow } from 'enzyme'; + +import { ImportCompleteView } from './import_complete_view'; + +jest.mock('../kibana_services', () => ({ + get: jest.fn(), + getDocLinks: () => { + return { + links: { + maps: { + importGeospatialPrivileges: 'linkToPrvilegesDocs', + }, + }, + }; + }, + getHttp: () => { + return { + basePath: { + prepend: (path: string) => `abc${path}`, + }, + }; + }, + getUiSettings: () => { + return { + get: jest.fn(), + }; + }, +})); + +test('Should render success', () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); + +test('Should render warning when some features failed import', () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); + +test('Should render error when upload fails from http request timeout', () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); + +test('Should render error when upload fails from elasticsearch request failure', () => { + const component = shallow( + + ); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/file_upload/public/components/import_complete_view.tsx b/x-pack/plugins/file_upload/public/components/import_complete_view.tsx index 46f566eb27e2e..f95aee869f93d 100644 --- a/x-pack/plugins/file_upload/public/components/import_complete_view.tsx +++ b/x-pack/plugins/file_upload/public/components/import_complete_view.tsx @@ -22,6 +22,7 @@ import { import { CodeEditor, KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { getDocLinks, getHttp, getUiSettings } from '../kibana_services'; import { ImportResults } from '../importer'; +import { getPartialImportMessage } from './utils'; const services = { uiSettings: getUiSettings(), @@ -133,7 +134,7 @@ export class ImportCompleteView extends Component { // Display http request error message reason = this.props.importResults.error.body.message; } else if (this.props.importResults?.error?.error?.reason) { - // Display elasticxsearch request error message + // Display elasticsearch request error message reason = this.props.importResults.error.error.reason; } const errorMsg = reason @@ -156,21 +157,25 @@ export class ImportCompleteView extends Component { ); } - const successMsg = i18n.translate('xpack.fileUpload.importComplete.uploadSuccessMsg', { - defaultMessage: 'Indexed {numFeatures} features.', - values: { - numFeatures: this.props.importResults.docCount, - }, - }); - - const failedFeaturesMsg = this.props.importResults.failures?.length - ? i18n.translate('xpack.fileUpload.importComplete.failedFeaturesMsg', { - defaultMessage: 'Unable to index {numFailures} features.', - values: { - numFailures: this.props.importResults.failures.length, - }, - }) - : ''; + if (this.props.importResults.failures?.length) { + return ( + +

+ {getPartialImportMessage( + this.props.importResults.failures!.length, + this.props.importResults.docCount + )} +

+
+ ); + } return ( { })} data-test-subj={STATUS_CALLOUT_DATA_TEST_SUBJ} > -

{`${successMsg} ${failedFeaturesMsg}`}

+

+ {i18n.translate('xpack.fileUpload.importComplete.uploadSuccessMsg', { + defaultMessage: 'Indexed {numFeatures} features.', + values: { + numFeatures: this.props.importResults.docCount, + }, + })} +

); } diff --git a/x-pack/plugins/file_upload/public/components/utils.ts b/x-pack/plugins/file_upload/public/components/utils.ts new file mode 100644 index 0000000000000..0b4df67f3a0ec --- /dev/null +++ b/x-pack/plugins/file_upload/public/components/utils.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export function getPartialImportMessage(failedFeaturesCount: number, totalFeaturesCount?: number) { + const outOfTotalMsg = + typeof totalFeaturesCount === 'number' + ? i18n.translate('xpack.fileUpload.geoUploadWizard.outOfTotalMsg', { + defaultMessage: 'of {totalFeaturesCount}', + values: { + totalFeaturesCount, + }, + }) + : ''; + return i18n.translate('xpack.fileUpload.geoUploadWizard.partialImportMsg', { + defaultMessage: 'Unable to index {failedFeaturesCount} {outOfTotalMsg} features.', + values: { + failedFeaturesCount, + outOfTotalMsg, + }, + }); +} diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index 8e818ddf206cc..50cef1fbe43cf 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -17,6 +17,7 @@ export const AGENT_POLICY_DEFAULT_MONITORING_DATASETS = [ 'elastic_agent.elastic_agent', 'elastic_agent.apm_server', 'elastic_agent.filebeat', + 'elastic_agent.filebeat_input', 'elastic_agent.fleet_server', 'elastic_agent.metricbeat', 'elastic_agent.osquerybeat', diff --git a/x-pack/plugins/fleet/common/constants/authz.ts b/x-pack/plugins/fleet/common/constants/authz.ts index fa2a2c0b44e93..c518dae975930 100644 --- a/x-pack/plugins/fleet/common/constants/authz.ts +++ b/x-pack/plugins/fleet/common/constants/authz.ts @@ -138,4 +138,10 @@ export const ENDPOINT_PRIVILEGES: Record = deepFreez privilegeType: 'api', privilegeName: 'writeFileOperations', }, + writeExecuteOperations: { + appId: DEFAULT_APP_CATEGORIES.security.id, + privilegeSplit: '-', + privilegeType: 'api', + privilegeName: 'writeExecuteOperations', + }, }); diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index bfc7208235c56..0a62474c89056 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -1372,6 +1372,14 @@ }, { "$ref": "#/components/parameters/with_metrics" + }, + { + "name": "getStatusSummary", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } } ], "security": [ @@ -5399,11 +5407,11 @@ "properties": { "cpu_avg": { "type": "number", - "description": "Average agent CPU usage during the last 5 minute, number between 0-1" + "description": "Average agent CPU usage during the last 5 minutes, number between 0-1" }, "memory_size_byte_avg": { "type": "number", - "description": "Average agent memory consumption during the last 5 minute" + "description": "Average agent memory consumption during the last 5 minutes" } } } @@ -5441,6 +5449,38 @@ }, "perPage": { "type": "number" + }, + "statusSummary": { + "type": "object", + "properties": { + "offline": { + "type": "number" + }, + "error": { + "type": "number" + }, + "online": { + "type": "number" + }, + "inactive": { + "type": "number" + }, + "enrolling": { + "type": "number" + }, + "unenrolling": { + "type": "number" + }, + "unenrolled": { + "type": "number" + }, + "updating": { + "type": "number" + }, + "degraded'": { + "type": "number" + } + } } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index bd8d18f2be5b1..7b93038b146bc 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -857,6 +857,11 @@ paths: - $ref: '#/components/parameters/sort_field' - $ref: '#/components/parameters/sort_order' - $ref: '#/components/parameters/with_metrics' + - name: getStatusSummary + in: query + required: false + schema: + type: boolean security: - basicAuth: [] /agents/bulk_upgrade: @@ -3421,11 +3426,11 @@ components: cpu_avg: type: number description: >- - Average agent CPU usage during the last 5 minute, number between - 0-1 + Average agent CPU usage during the last 5 minutes, number + between 0-1 memory_size_byte_avg: type: number - description: Average agent memory consumption during the last 5 minute + description: Average agent memory consumption during the last 5 minutes required: - type - active @@ -3451,6 +3456,27 @@ components: type: number perPage: type: number + statusSummary: + type: object + properties: + offline: + type: number + error: + type: number + online: + type: number + inactive: + type: number + enrolling: + type: number + unenrolling: + type: number + unenrolled: + type: number + updating: + type: number + degraded': + type: number required: - items - total diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/get_agents_response.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/get_agents_response.yaml index 7a4be64d29cbe..71416b6d4fe7a 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/get_agents_response.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/get_agents_response.yaml @@ -16,6 +16,27 @@ properties: type: number perPage: type: number + statusSummary: + type: object + properties: + offline: + type: number + error: + type: number + online : + type: number + inactive : + type: number + enrolling: + type: number + unenrolling: + type: number + unenrolled : + type: number + updating : + type: number + degraded': + type: number required: - items - total diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml index cc1aaab2a679c..44d5e1c8a9738 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents.yaml @@ -18,5 +18,10 @@ get: - $ref: ../components/parameters/sort_field.yaml - $ref: ../components/parameters/sort_order.yaml - $ref: ../components/parameters/with_metrics.yaml + - name: getStatusSummary + in: query + required: false + schema: + type: boolean security: - basicAuth: [] diff --git a/x-pack/plugins/fleet/common/services/agent_statuses_to_summary.test.ts b/x-pack/plugins/fleet/common/services/agent_statuses_to_summary.test.ts new file mode 100644 index 0000000000000..49144a75b5691 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/agent_statuses_to_summary.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { agentStatusesToSummary } from './agent_statuses_to_summary'; + +describe('agentStatusesToSummary', () => { + it('should return the correct summary', () => { + expect( + agentStatusesToSummary({ + online: 1, + error: 2, + degraded: 3, + inactive: 4, + offline: 5, + updating: 6, + enrolling: 7, + unenrolling: 8, + unenrolled: 9, + }) + ).toEqual({ + healthy: 1, + unhealthy: 5, + inactive: 4, + offline: 5, + updating: 21, + unenrolled: 9, + }); + }); +}); diff --git a/x-pack/plugins/fleet/common/services/agent_statuses_to_summary.ts b/x-pack/plugins/fleet/common/services/agent_statuses_to_summary.ts new file mode 100644 index 0000000000000..0c1f0311851d4 --- /dev/null +++ b/x-pack/plugins/fleet/common/services/agent_statuses_to_summary.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AgentStatus, SimplifiedAgentStatus } from '../types'; + +export function agentStatusesToSummary( + statuses: Record +): Record { + return { + healthy: statuses.online, + unhealthy: statuses.error + statuses.degraded, + inactive: statuses.inactive, + offline: statuses.offline, + updating: statuses.updating + statuses.enrolling + statuses.unenrolling, + unenrolled: statuses.unenrolled, + }; +} diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts index 6f630cf117e29..2b5a8dd04bc91 100644 --- a/x-pack/plugins/fleet/common/services/index.ts +++ b/x-pack/plugins/fleet/common/services/index.ts @@ -58,3 +58,4 @@ export { } from './package_prerelease'; export { getAllowedOutputTypeForPolicy } from './output_helpers'; +export { agentStatusesToSummary } from './agent_statuses_to_summary'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts index 68b251f21e294..10866da70d93e 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/agent.ts @@ -14,6 +14,7 @@ import type { CurrentUpgrade, NewAgentAction, AgentDiagnostics, + AgentStatus, } from '../models'; import type { ListResult, ListWithKuery } from './common'; @@ -27,9 +28,9 @@ export interface GetAgentsRequest { } export interface GetAgentsResponse extends ListResult { - totalInactive: number; // deprecated in 8.x list?: Agent[]; + statusSummary?: Record; } export interface GetAgentTagsResponse { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx index 24277e464582c..8d7259a77767f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -106,7 +106,11 @@ export function useOnSubmit({ // Form state const [formState, setFormState] = useState('VALID'); + // Used to render extension components only when package policy is initialized + const [isInitialized, setIsInitialized] = useState(false); + // Used to initialize the package policy once const isInitializedRef = useRef(false); + const [agentPolicy, setAgentPolicy] = useState(); // New package policy state const [packagePolicy, setPackagePolicy] = useState({ @@ -204,15 +208,16 @@ export function useOnSubmit({ packageToPackagePolicy( packageInfo, agentPolicy?.id || '', - DEFAULT_PACKAGE_POLICY.namespace, + agentPolicy?.namespace || DEFAULT_PACKAGE_POLICY.namespace, DEFAULT_PACKAGE_POLICY.name || incrementedName, DEFAULT_PACKAGE_POLICY.description, integrationToEnable ) ); + setIsInitialized(true); } init(); - }, [packageInfo, agentPolicy, updatePackagePolicy, integrationToEnable]); + }, [packageInfo, agentPolicy, updatePackagePolicy, integrationToEnable, isInitialized]); const onSaveNavigate = useOnSaveNavigate({ packagePolicy, @@ -360,7 +365,7 @@ export function useOnSubmit({ setValidationResults, hasAgentPolicyError, setHasAgentPolicyError, - isInitialized: isInitializedRef.current, + isInitialized, // TODO check navigateAddAgent, navigateAddAgentHelp, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index cd5b997d9c788..9a1c943763bd8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -267,6 +267,29 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ ); const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); + const replaceDefineStepView = useUIExtension( + packagePolicy.package?.name ?? '', + 'package-policy-replace-define-step' + ); + + if (replaceDefineStepView && extensionView) { + throw new Error( + "'package-policy-create' and 'package-policy-replace-define-step' cannot both be registered as UI extensions" + ); + } + + const replaceStepConfigurePackagePolicy = replaceDefineStepView && packageInfo?.name && ( + + + + ); const stepConfigurePackagePolicy = useMemo( () => @@ -329,7 +352,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ defaultMessage: 'Configure integration', }), 'data-test-subj': 'dataCollectionSetupStep', - children: stepConfigurePackagePolicy, + children: replaceStepConfigurePackagePolicy || stepConfigurePackagePolicy, }, { title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle', { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx index c295cdd41cecb..303629a99bb69 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx @@ -331,11 +331,42 @@ describe('edit package policy page', () => { expect(useStartServices().application.navigateToUrl).not.toHaveBeenCalled(); }); - it('should show ready for upgrade if package useLatestPackageVersion and no conflicts', async () => { - (useUIExtension as MockFn).mockReturnValue({ - useLatestPackageVersion: true, - Component: TestComponent, + it("throws when both 'package-policy-edit' and 'package-policy-replace-define-step' are defined", async () => { + (useUIExtension as MockFn) + .mockReturnValueOnce({ + view: 'package-policy-edit', + Component: TestComponent, + }) + .mockReturnValueOnce({ + view: 'package-policy-replace-define-step', + Component: TestComponent, + }) + .mockReturnValueOnce({ + view: 'package-policy-edit-tabs', + Component: TestComponent, + }); + + render(); + + await waitFor(() => { + expect(renderResult.getByTestId('euiErrorBoundary')).toBeVisible(); }); + }); + + it('should show ready for upgrade if package useLatestPackageVersion and no conflicts', async () => { + (useUIExtension as MockFn) + .mockReturnValueOnce({ + view: 'package-policy-edit', + useLatestPackageVersion: true, + Component: TestComponent, + }) + .mockReturnValueOnce(undefined) + .mockReturnValueOnce({ + view: 'package-policy-edit-tabs', + useLatestPackageVersion: true, + Component: TestComponent, + }); + (sendUpgradePackagePolicyDryRun as MockFn).mockResolvedValue({ data: [ { @@ -357,10 +388,19 @@ describe('edit package policy page', () => { }); it('should show review field conflicts if package useLatestPackageVersion and has conflicts', async () => { - (useUIExtension as MockFn).mockReturnValue({ - useLatestPackageVersion: true, - Component: TestComponent, - }); + (useUIExtension as MockFn) + .mockReturnValueOnce({ + view: 'package-policy-edit', + useLatestPackageVersion: true, + Component: TestComponent, + }) + .mockReturnValueOnce(undefined) + .mockReturnValueOnce({ + view: 'package-policy-edit-tabs', + useLatestPackageVersion: true, + Component: TestComponent, + }); + (sendUpgradePackagePolicyDryRun as MockFn).mockResolvedValue({ data: [ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index afab7a48ebb76..b066457b0aa2e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -35,7 +35,7 @@ import { } from '../../../../integrations/hooks'; import { Loading, - Error, + Error as ErrorComponent, ExtensionWrapper, EuiButtonWithTooltip, DevtoolsRequestFlyoutButton, @@ -240,10 +240,21 @@ export const EditPackagePolicyForm = memo<{ }; const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit'); + const replaceDefineStepView = useUIExtension( + packagePolicy.package?.name ?? '', + 'package-policy-replace-define-step' + ); const extensionTabsView = useUIExtension( packagePolicy.package?.name ?? '', 'package-policy-edit-tabs' ); + + if (replaceDefineStepView && extensionView) { + throw new Error( + "'package-policy-create' and 'package-policy-replace-define-step' cannot both be registered as UI extensions" + ); + } + const tabsViews = extensionTabsView?.tabs; const [selectedTab, setSelectedTab] = useState(0); @@ -339,6 +350,20 @@ export const EditPackagePolicyForm = memo<{ ] ); + const replaceConfigurePackage = replaceDefineStepView && originalPackagePolicy && packageInfo && ( + + + + ); + const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = ExperimentalFeaturesService.get(); @@ -361,7 +386,7 @@ export const EditPackagePolicyForm = memo<{ {isLoadingData ? ( ) : loadingError || !agentPolicy || !packageInfo ? ( - )} - {configurePackage} + {replaceConfigurePackage || configurePackage} {/* Extra space to accomodate the EuiBottomBar height */} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx index 6e504c5e183cf..e7095e7af5a4c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout.tsx @@ -294,7 +294,7 @@ const formattedTime = (time?: string) => { const inProgressTitle = (action: ActionStatus) => ( = action.nbAgentsActioned @@ -307,6 +307,7 @@ const inProgressTitle = (action: ActionStatus) => ( reassignText: action.type === 'POLICY_REASSIGN' && action.newPolicyId ? `to ${action.newPolicyId}` : '', upgradeText: action.type === 'UPGRADE' ? `to version ${action.version}` : '', + failuresText: action.nbAgentsFailed > 0 ? `, has ${action.nbAgentsFailed} failure(s)` : '', }} /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx index 1e3bb5ef50b7b..3faaa6e64d26a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx @@ -128,6 +128,7 @@ export const AgentListTable: React.FC = (props: Props) => { { field: 'policy_id', sortable: true, + truncateText: true, name: i18n.translate('xpack.fleet.agentList.policyColumnTitle', { defaultMessage: 'Agent policy', }), diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_last_seen_inactive_agents_count.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_last_seen_inactive_agents_count.ts index ce767c82eec37..e1baf8f277ff1 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_last_seen_inactive_agents_count.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/hooks/use_last_seen_inactive_agents_count.ts @@ -11,7 +11,6 @@ const LOCAL_STORAGE_KEY = 'fleet.lastSeenInactiveAgentsCount'; export const useLastSeenInactiveAgentsCount = (): [number, (val: number) => void] => { const [lastSeenInactiveAgentsCount, setLastSeenInactiveAgentsCount] = useState(0); - useEffect(() => { const storageValue = localStorage.getItem(LOCAL_STORAGE_KEY); if (storageValue) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx index cefb6ae1c3f9b..2bc453a45a888 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.test.tsx @@ -100,14 +100,18 @@ describe('agent_list_page', () => { data: { items: mapAgents(['agent1', 'agent2', 'agent3', 'agent4', 'agent5']), total: 6, - totalInactive: 0, + statusSummary: { + online: 6, + }, }, }) .mockResolvedValueOnce({ data: { items: mapAgents(['agent1', 'agent2', 'agent3', 'agent4', 'agent6']), total: 6, - totalInactive: 0, + statusSummary: { + online: 6, + }, }, }); jest.useFakeTimers({ legacyFakeTimers: true }); @@ -128,12 +132,8 @@ describe('agent_list_page', () => { return { data: { results: { - online: 6, - error: 0, - offline: 0, - updating: 0, + inactive: 0, }, - totalInactive: 0, }, }; }); @@ -151,10 +151,7 @@ describe('agent_list_page', () => { mockedSendGetAgentStatus.mockResolvedValue({ data: { results: { - online: 6, - error: 0, - offline: 0, - updating: 0, + inactive: 0, }, totalInactive: 0, }, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index ee77846bce93c..f6808ff2fa327 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 @@ -13,6 +13,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { agentStatusesToSummary } from '../../../../../../common/services'; + import type { Agent, AgentPolicy, SimplifiedAgentStatus } from '../../../types'; import { usePagination, @@ -226,7 +228,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const [totalAgents, setTotalAgents] = useState(0); const [totalInactiveAgents, setTotalInactiveAgents] = useState(0); const [showAgentActivityTour, setShowAgentActivityTour] = useState({ isOpen: false }); - const getSortFieldForAPI = (field: keyof Agent): string => { if ([VERSION_FIELD, HOSTNAME_FIELD].includes(field as string)) { return `${field}.keyword`; @@ -275,25 +276,27 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { isLoadingVar.current = true; try { setIsLoading(true); - const [agentsResponse, agentsStatusResponse, agentTagsResponse] = await Promise.all([ - sendGetAgents({ - page: pagination.currentPage, - perPage: pagination.pageSize, - kuery: kuery && kuery !== '' ? kuery : undefined, - sortField: getSortFieldForAPI(sortField), - sortOrder, - showInactive, - showUpgradeable, - withMetrics: displayAgentMetrics, - }), - sendGetAgentStatus({ - kuery: kuery && kuery !== '' ? kuery : undefined, - }), - sendGetAgentTags({ - kuery: kuery && kuery !== '' ? kuery : undefined, - showInactive, - }), - ]); + const [agentsResponse, totalInactiveAgentsResponse, agentTagsResponse] = + await Promise.all([ + sendGetAgents({ + page: pagination.currentPage, + perPage: pagination.pageSize, + kuery: kuery && kuery !== '' ? kuery : undefined, + sortField: getSortFieldForAPI(sortField), + sortOrder, + showInactive, + showUpgradeable, + getStatusSummary: true, + withMetrics: displayAgentMetrics, + }), + sendGetAgentStatus({ + kuery: AgentStatusKueryHelper.buildKueryForInactiveAgents(), + }), + sendGetAgentTags({ + kuery: kuery && kuery !== '' ? kuery : undefined, + showInactive, + }), + ]); isLoadingVar.current = false; // Return if a newer request has been triggered if (currentRequestRef.current !== currentRequest) { @@ -305,27 +308,22 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { if (!agentsResponse.data) { throw new Error('Invalid GET /agents response'); } - if (agentsStatusResponse.error) { - throw agentsStatusResponse.error; - } - if (!agentsStatusResponse.data) { + if (!totalInactiveAgentsResponse.data) { throw new Error('Invalid GET /agents_status response'); } if (agentTagsResponse.error) { - throw agentsStatusResponse.error; + throw agentTagsResponse.error; } if (!agentTagsResponse.data) { throw new Error('Invalid GET /agent/tags response'); } - setAgentsStatus({ - healthy: agentsStatusResponse.data.results.online, - unhealthy: agentsStatusResponse.data.results.error, - offline: agentsStatusResponse.data.results.offline, - updating: agentsStatusResponse.data.results.updating, - inactive: agentsStatusResponse.data.results.inactive, - unenrolled: agentsStatusResponse.data.results.unenrolled, - }); + const statusSummary = agentsResponse.data.statusSummary; + + if (!statusSummary) { + throw new Error('Invalid GET /agents response - no status summary'); + } + setAgentsStatus(agentStatusesToSummary(statusSummary)); const newAllTags = agentTagsResponse.data.items; @@ -339,7 +337,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { setAgents(agentsResponse.data.items); setTotalAgents(agentsResponse.data.total); - setTotalInactiveAgents(agentsResponse.data.totalInactive); + setTotalInactiveAgents(totalInactiveAgentsResponse.data.results.inactive || 0); } catch (error) { notifications.toasts.addError(error, { title: i18n.translate('xpack.fleet.agentList.errorFetchingDataTitle', { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx deleted file mode 100644 index ccb53ac7dc355..0000000000000 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.tsx +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ReactNode, FunctionComponent } from 'react'; -import { useMemo } from 'react'; -import React, { useCallback, useState } from 'react'; -import { css } from '@emotion/react'; - -import { - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiLink, - EuiSpacer, - EuiTitle, - EuiFieldSearch, - EuiText, - useEuiTheme, - EuiIcon, - EuiScreenReaderOnly, - EuiButton, - EuiButtonIcon, - EuiPopover, - EuiContextMenuPanel, - EuiContextMenuItem, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { FormattedMessage } from '@kbn/i18n-react'; - -import { Loading } from '../../../components'; -import { useLocalSearch, searchIdField } from '../../../hooks'; - -import type { IntegrationCardItem } from '../../../../../../common/types/models'; - -import type { ExtendedIntegrationCategory, CategoryFacet } from '../screens/home/category_facets'; - -import type { IntegrationsURLParameters } from '../screens/home/hooks/use_available_packages'; - -import { ExperimentalFeaturesService } from '../../../services'; - -import { promoteFeaturedIntegrations } from './utils'; - -import { PackageCard } from './package_card'; - -export interface Props { - isLoading?: boolean; - controls?: ReactNode | ReactNode[]; - list: IntegrationCardItem[]; - searchTerm: string; - setSearchTerm: (search: string) => void; - selectedCategory: ExtendedIntegrationCategory; - setCategory: (category: ExtendedIntegrationCategory) => void; - categories: CategoryFacet[]; - setUrlandReplaceHistory: (params: IntegrationsURLParameters) => void; - setUrlandPushHistory: (params: IntegrationsURLParameters) => void; - callout?: JSX.Element | null; - // Props used only in AvailablePackages component: - showCardLabels?: boolean; - title?: string; - availableSubCategories?: CategoryFacet[]; - selectedSubCategory?: string; - setSelectedSubCategory?: (c: string | undefined) => void; - showMissingIntegrationMessage?: boolean; -} - -export const PackageListGrid: FunctionComponent = ({ - isLoading, - controls, - title, - list, - searchTerm, - setSearchTerm, - selectedCategory, - setCategory, - categories, - availableSubCategories, - setSelectedSubCategory, - selectedSubCategory, - setUrlandReplaceHistory, - setUrlandPushHistory, - showMissingIntegrationMessage = false, - callout, - showCardLabels = true, -}) => { - const localSearchRef = useLocalSearch(list); - const { euiTheme } = useEuiTheme(); - - const [isPopoverOpen, setPopover] = useState(false); - - const MAX_SUBCATEGORIES_NUMBER = 6; - - const { showIntegrationsSubcategories } = ExperimentalFeaturesService.get(); - - const onButtonClick = () => { - setPopover(!isPopoverOpen); - }; - - const closePopover = () => { - setPopover(false); - }; - - const onQueryChange = (e: any) => { - const queryText = e.target.value; - setSearchTerm(queryText); - setUrlandReplaceHistory({ - searchString: queryText, - categoryId: selectedCategory, - subCategoryId: selectedSubCategory, - }); - }; - - const resetQuery = () => { - setSearchTerm(''); - setUrlandReplaceHistory({ searchString: '', categoryId: '', subCategoryId: '' }); - }; - - const onSubCategoryClick = useCallback( - (subCategory: string) => { - if (setSelectedSubCategory) setSelectedSubCategory(subCategory); - setUrlandPushHistory({ - categoryId: selectedCategory, - subCategoryId: subCategory, - }); - }, - [selectedCategory, setSelectedSubCategory, setUrlandPushHistory] - ); - - const selectedCategoryTitle = selectedCategory - ? categories.find((category) => category.id === selectedCategory)?.title - : undefined; - - const filteredPromotedList = useMemo(() => { - if (isLoading) return []; - const filteredList = searchTerm - ? list.filter((item) => - (localSearchRef.current!.search(searchTerm) as IntegrationCardItem[]) - .map((match) => match[searchIdField]) - .includes(item[searchIdField]) - ) - : list; - - return promoteFeaturedIntegrations(filteredList, selectedCategory); - }, [isLoading, list, localSearchRef, searchTerm, selectedCategory]); - - const splitSubcategories = ( - subcategories: CategoryFacet[] | undefined - ): { visibleSubCategories?: CategoryFacet[]; hiddenSubCategories?: CategoryFacet[] } => { - if (!subcategories) return {}; - else if (subcategories && subcategories?.length < MAX_SUBCATEGORIES_NUMBER) { - return { visibleSubCategories: subcategories, hiddenSubCategories: [] }; - } else if (subcategories && subcategories?.length >= MAX_SUBCATEGORIES_NUMBER) { - return { - visibleSubCategories: subcategories.slice(0, MAX_SUBCATEGORIES_NUMBER), - hiddenSubCategories: subcategories.slice(MAX_SUBCATEGORIES_NUMBER), - }; - } - return {}; - }; - - const splitSubcat = splitSubcategories(availableSubCategories); - const { visibleSubCategories } = splitSubcat; - const hiddenSubCategoriesItems = useMemo(() => { - return splitSubcat?.hiddenSubCategories?.map((subCategory) => { - return ( - { - onSubCategoryClick(subCategory.id); - closePopover(); - }} - > - {subCategory.title} - - ); - }); - }, [onSubCategoryClick, splitSubcat.hiddenSubCategories]); - - return ( - - - - - - onQueryChange(e)} - isClearable={true} - incremental={true} - fullWidth={true} - prepend={ - selectedCategoryTitle ? ( - - - Searching category: - - {selectedCategoryTitle} - - - ) : undefined - } - /> - {showIntegrationsSubcategories && availableSubCategories?.length ? : null} - {showIntegrationsSubcategories ? ( - - {visibleSubCategories?.map((subCategory) => ( - - onSubCategoryClick(subCategory.id)} - > - - - - ))} - {hiddenSubCategoriesItems?.length ? ( - - - } - isOpen={isPopoverOpen} - closePopover={closePopover} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - - - ) : null} - - ) : null} - {callout ? ( - <> - - {callout} - - ) : null} - - - {showMissingIntegrationMessage && ( - <> - - - - - )} - - - ); -}; - -interface ControlsColumnProps { - controls: ReactNode; - title: string | undefined; -} - -const ControlsColumn = ({ controls, title }: ControlsColumnProps) => { - let titleContent; - if (title) { - titleContent = ( - <> - -

{title}

-
- - - ); - } - return ( - - {titleContent} - {controls} - - ); -}; - -interface GridColumnProps { - list: IntegrationCardItem[]; - isLoading: boolean; - showMissingIntegrationMessage?: boolean; - showCardLabels?: boolean; -} - -const GridColumn = ({ - list, - showMissingIntegrationMessage = false, - showCardLabels = false, - isLoading, -}: GridColumnProps) => { - if (isLoading) return ; - - return ( - - {list.length ? ( - list.map((item) => { - return ( - .euiPopover, - & > .euiPopover > .euiPopover__anchor, - & > .euiPopover > .euiPopover__anchor > .euiCard { - height: 100%; - } - `} - > - - - ); - }) - ) : ( - - -

- {showMissingIntegrationMessage ? ( - - ) : ( - - )} -

-
-
- )} -
- ); -}; - -interface MissingIntegrationContentProps { - resetQuery: () => void; - setSelectedCategory: (category: ExtendedIntegrationCategory) => void; - setUrlandPushHistory: (params: IntegrationsURLParameters) => void; -} - -const MissingIntegrationContent = ({ - resetQuery, - setSelectedCategory, - setUrlandPushHistory, -}: MissingIntegrationContentProps) => { - const handleCustomInputsLinkClick = useCallback(() => { - resetQuery(); - setSelectedCategory('custom'); - setUrlandPushHistory({ - categoryId: 'custom', - subCategoryId: '', - }); - }, [resetQuery, setSelectedCategory, setUrlandPushHistory]); - - return ( - -

- - - - ), - forumLink: ( - - - - ), - }} - /> -

-
- ); -}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx new file mode 100644 index 0000000000000..1466cd6cb9dc5 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/controls.tsx @@ -0,0 +1,165 @@ +/* + * 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 { ReactNode } from 'react'; +import React, { useCallback } from 'react'; +import { css } from '@emotion/react'; + +import { + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSpacer, + EuiTitle, + EuiText, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { Loading } from '../../../../components'; + +import type { IntegrationCardItem } from '../../../../../../../common/types/models'; + +import type { ExtendedIntegrationCategory } from '../../screens/home/category_facets'; + +import type { IntegrationsURLParameters } from '../../screens/home/hooks/use_available_packages'; + +import { PackageCard } from '../package_card'; + +interface ControlsColumnProps { + controls: ReactNode; + title: string | undefined; +} + +export const ControlsColumn = ({ controls, title }: ControlsColumnProps) => { + let titleContent; + if (title) { + titleContent = ( + <> + +

{title}

+
+ + + ); + } + return ( + + {titleContent} + {controls} + + ); +}; + +interface GridColumnProps { + list: IntegrationCardItem[]; + isLoading: boolean; + showMissingIntegrationMessage?: boolean; + showCardLabels?: boolean; +} + +export const GridColumn = ({ + list, + showMissingIntegrationMessage = false, + showCardLabels = false, + isLoading, +}: GridColumnProps) => { + if (isLoading) return ; + + return ( + + {list.length ? ( + list.map((item) => { + return ( + .euiPopover, + & > .euiPopover > .euiPopover__anchor, + & > .euiPopover > .euiPopover__anchor > .euiCard { + height: 100%; + } + `} + > + + + ); + }) + ) : ( + + +

+ {showMissingIntegrationMessage ? ( + + ) : ( + + )} +

+
+
+ )} +
+ ); +}; + +interface MissingIntegrationContentProps { + resetQuery: () => void; + setSelectedCategory: (category: ExtendedIntegrationCategory) => void; + setUrlandPushHistory: (params: IntegrationsURLParameters) => void; +} + +export const MissingIntegrationContent = ({ + resetQuery, + setSelectedCategory, + setUrlandPushHistory, +}: MissingIntegrationContentProps) => { + const handleCustomInputsLinkClick = useCallback(() => { + resetQuery(); + setSelectedCategory('custom'); + setUrlandPushHistory({ + categoryId: 'custom', + subCategoryId: '', + }); + }, [resetQuery, setSelectedCategory, setUrlandPushHistory]); + + return ( + +

+ + + + ), + forumLink: ( + + + + ), + }} + /> +

+
+ ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.stories.tsx similarity index 97% rename from x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx rename to x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.stories.tsx index 1ff0c435693e2..8aabb1771680a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid.stories.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.stories.tsx @@ -9,8 +9,8 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; -import type { Props } from './package_list_grid'; -import { PackageListGrid } from './package_list_grid'; +import type { Props } from '.'; +import { PackageListGrid } from '.'; export default { component: PackageListGrid, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx new file mode 100644 index 0000000000000..d3d283accf4d0 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/index.tsx @@ -0,0 +1,272 @@ +/* + * 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 { ReactNode, FunctionComponent } from 'react'; +import { useMemo } from 'react'; +import React, { useCallback, useState } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiButton, + EuiButtonIcon, + EuiPopover, + EuiContextMenuPanel, + EuiContextMenuItem, +} from '@elastic/eui'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useLocalSearch, searchIdField } from '../../../../hooks'; + +import type { IntegrationCardItem } from '../../../../../../../common/types/models'; + +import type { + ExtendedIntegrationCategory, + CategoryFacet, +} from '../../screens/home/category_facets'; + +import type { IntegrationsURLParameters } from '../../screens/home/hooks/use_available_packages'; + +import { ExperimentalFeaturesService } from '../../../../services'; + +import { promoteFeaturedIntegrations } from '../utils'; + +import { ControlsColumn, MissingIntegrationContent, GridColumn } from './controls'; +import { SearchBox } from './search_box'; + +export interface Props { + isLoading?: boolean; + controls?: ReactNode | ReactNode[]; + list: IntegrationCardItem[]; + searchTerm: string; + setSearchTerm: (search: string) => void; + selectedCategory: ExtendedIntegrationCategory; + setCategory: (category: ExtendedIntegrationCategory) => void; + categories: CategoryFacet[]; + setUrlandReplaceHistory: (params: IntegrationsURLParameters) => void; + setUrlandPushHistory: (params: IntegrationsURLParameters) => void; + callout?: JSX.Element | null; + // Props used only in AvailablePackages component: + showCardLabels?: boolean; + title?: string; + availableSubCategories?: CategoryFacet[]; + selectedSubCategory?: string; + setSelectedSubCategory?: (c: string | undefined) => void; + showMissingIntegrationMessage?: boolean; +} + +export const PackageListGrid: FunctionComponent = ({ + isLoading, + controls, + title, + list, + searchTerm, + setSearchTerm, + selectedCategory, + setCategory, + categories, + availableSubCategories, + setSelectedSubCategory, + selectedSubCategory, + setUrlandReplaceHistory, + setUrlandPushHistory, + showMissingIntegrationMessage = false, + callout, + showCardLabels = true, +}) => { + const localSearchRef = useLocalSearch(list); + + const [isPopoverOpen, setPopover] = useState(false); + + const MAX_SUBCATEGORIES_NUMBER = 6; + + const { showIntegrationsSubcategories } = ExperimentalFeaturesService.get(); + + const onButtonClick = () => { + setPopover(!isPopoverOpen); + }; + + const closePopover = () => { + setPopover(false); + }; + + const resetQuery = () => { + setSearchTerm(''); + setUrlandReplaceHistory({ searchString: '', categoryId: '', subCategoryId: '' }); + }; + + const onSubCategoryClick = useCallback( + (subCategory: string) => { + if (setSelectedSubCategory) setSelectedSubCategory(subCategory); + setUrlandPushHistory({ + categoryId: selectedCategory, + subCategoryId: subCategory, + }); + }, + [selectedCategory, setSelectedSubCategory, setUrlandPushHistory] + ); + + const filteredPromotedList = useMemo(() => { + if (isLoading) return []; + const filteredList = searchTerm + ? list.filter((item) => + (localSearchRef.current!.search(searchTerm) as IntegrationCardItem[]) + .map((match) => match[searchIdField]) + .includes(item[searchIdField]) + ) + : list; + + return promoteFeaturedIntegrations(filteredList, selectedCategory); + }, [isLoading, list, localSearchRef, searchTerm, selectedCategory]); + + const splitSubcategories = ( + subcategories: CategoryFacet[] | undefined + ): { visibleSubCategories?: CategoryFacet[]; hiddenSubCategories?: CategoryFacet[] } => { + if (!subcategories) return {}; + else if (subcategories && subcategories?.length < MAX_SUBCATEGORIES_NUMBER) { + return { visibleSubCategories: subcategories, hiddenSubCategories: [] }; + } else if (subcategories && subcategories?.length >= MAX_SUBCATEGORIES_NUMBER) { + return { + visibleSubCategories: subcategories.slice(0, MAX_SUBCATEGORIES_NUMBER), + hiddenSubCategories: subcategories.slice(MAX_SUBCATEGORIES_NUMBER), + }; + } + return {}; + }; + + const splitSubcat = splitSubcategories(availableSubCategories); + const { visibleSubCategories } = splitSubcat; + const hiddenSubCategoriesItems = useMemo(() => { + return splitSubcat?.hiddenSubCategories?.map((subCategory) => { + return ( + { + onSubCategoryClick(subCategory.id); + closePopover(); + }} + icon={selectedSubCategory === subCategory.id ? 'check' : 'empty'} + > + {subCategory.title} + + ); + }); + }, [onSubCategoryClick, selectedSubCategory, splitSubcat?.hiddenSubCategories]); + + return ( + + + + + + + {showIntegrationsSubcategories && availableSubCategories?.length ? : null} + {showIntegrationsSubcategories ? ( + + {visibleSubCategories?.map((subCategory) => { + const isSelected = subCategory.id === selectedSubCategory; + return ( + + onSubCategoryClick(subCategory.id)} + > + + + + ); + })} + {hiddenSubCategoriesItems?.length ? ( + + + } + isOpen={isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + + ) : null} + + ) : null} + {callout ? ( + <> + + {callout} + + ) : null} + + + {showMissingIntegrationMessage && ( + <> + + + + + )} + + + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx new file mode 100644 index 0000000000000..593ad3e60e82b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/package_list_grid/search_box.tsx @@ -0,0 +1,134 @@ +/* + * 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 { FunctionComponent } from 'react'; +import React, { useMemo } from 'react'; + +import { EuiFieldSearch, EuiText, useEuiTheme, EuiIcon, EuiScreenReaderOnly } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import type { + ExtendedIntegrationCategory, + CategoryFacet, +} from '../../screens/home/category_facets'; + +import type { IntegrationsURLParameters } from '../../screens/home/hooks/use_available_packages'; + +export interface Props { + searchTerm: string; + setSearchTerm: (search: string) => void; + selectedCategory: ExtendedIntegrationCategory; + setCategory: (category: ExtendedIntegrationCategory) => void; + categories: CategoryFacet[]; + availableSubCategories?: CategoryFacet[]; + setUrlandReplaceHistory: (params: IntegrationsURLParameters) => void; + selectedSubCategory?: string; + setSelectedSubCategory?: (c: string | undefined) => void; +} + +export const SearchBox: FunctionComponent = ({ + searchTerm, + setSearchTerm, + selectedCategory, + setCategory, + categories, + availableSubCategories, + setSelectedSubCategory, + selectedSubCategory, + setUrlandReplaceHistory, +}) => { + const { euiTheme } = useEuiTheme(); + + const onQueryChange = (e: any) => { + const queryText = e.target.value; + setSearchTerm(queryText); + setUrlandReplaceHistory({ + searchString: queryText, + categoryId: selectedCategory, + subCategoryId: selectedSubCategory, + }); + }; + + const selectedCategoryTitle = selectedCategory + ? categories.find((category) => category.id === selectedCategory)?.title + : undefined; + + const getCategoriesLabel = useMemo(() => { + const selectedSubCategoryTitle = + selectedSubCategory && availableSubCategories + ? availableSubCategories.find((subCat) => subCat.id === selectedSubCategory)?.title + : undefined; + + if (selectedCategoryTitle && selectedSubCategoryTitle) { + return `${selectedCategoryTitle}, ${selectedSubCategoryTitle}`; + } else if (selectedCategoryTitle) { + return `${selectedCategoryTitle}`; + } else return ''; + }, [availableSubCategories, selectedCategoryTitle, selectedSubCategory]); + + return ( + onQueryChange(e)} + isClearable={true} + incremental={true} + fullWidth={true} + prepend={ + selectedCategoryTitle ? ( + + + Searching category: + + {getCategoriesLabel} + + + ) : undefined + } + /> + ); +}; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx index c1190a66c7034..f13fd592daccf 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx @@ -5,23 +5,20 @@ * 2.0. */ import React, { useState, useMemo } from 'react'; -import { useLocation, useParams, useHistory } from 'react-router-dom'; import { uniq, xorBy } from 'lodash'; import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common'; import type { IntegrationPreferenceType } from '../../../components/integration_preference'; -import { usePackages, useCategories, useStartServices } from '../../../../../hooks'; +import { usePackages, useCategories } from '../../../../../hooks'; import { useGetAppendCustomIntegrations, useGetReplacementCustomIntegrations, - useLink, } from '../../../../../hooks'; import { useMergeEprPackagesWithReplacements } from '../../../../../hooks/use_merge_epr_with_replacements'; -import type { CategoryParams } from '..'; -import { getParams, mapToCard } from '..'; +import { mapToCard } from '..'; import type { PackageList, PackageListItem } from '../../../../../types'; import { doesPackageHaveIntegrations } from '../../../../../services'; @@ -31,8 +28,6 @@ import { isIntegrationPolicyTemplate, } from '../../../../../../../../common/services'; -import { pagePathGetters } from '../../../../../constants'; - import type { IntegrationCardItem } from '../../../../../../../../common/types/models'; import { ALL_CATEGORY } from '../category_facets'; @@ -40,6 +35,8 @@ import type { CategoryFacet } from '../category_facets'; import { mergeCategoriesAndCount } from '../util'; +import { useBuildIntegrationsUrl } from './use_build_integrations_url'; + export interface IntegrationsURLParameters { searchString?: string; categoryId?: string; @@ -111,14 +108,17 @@ export const useAvailablePackages = () => { const [prereleaseIntegrationsEnabled, setPrereleaseIntegrationsEnabled] = React.useState< boolean | undefined >(undefined); - const { http } = useStartServices(); - const addBasePath = http.basePath.prepend; const { - selectedCategory: initialSelectedCategory, - selectedSubcategory: initialSubcategory, + initialSelectedCategory, + initialSubcategory, + setUrlandPushHistory, + setUrlandReplaceHistory, + getHref, + getAbsolutePath, searchParam, - } = getParams(useParams(), useLocation().search); + addBasePath, + } = useBuildIntegrationsUrl(); const [selectedCategory, setCategory] = useState(initialSelectedCategory); const [selectedSubCategory, setSelectedSubCategory] = useState( @@ -126,45 +126,6 @@ export const useAvailablePackages = () => { ); const [searchTerm, setSearchTerm] = useState(searchParam || ''); - const { getHref, getAbsolutePath } = useLink(); - const history = useHistory(); - - const buildUrl = ({ searchString, categoryId, subCategoryId }: IntegrationsURLParameters) => { - const url = pagePathGetters.integrations_all({ - category: categoryId ? categoryId : '', - subCategory: subCategoryId ? subCategoryId : '', - searchTerm: searchString ? searchString : '', - })[1]; - return url; - }; - - const setUrlandPushHistory = ({ - searchString, - categoryId, - subCategoryId, - }: IntegrationsURLParameters) => { - const url = buildUrl({ - categoryId, - searchString, - subCategoryId, - }); - history.push(url); - }; - - const setUrlandReplaceHistory = ({ - searchString, - categoryId, - subCategoryId, - }: IntegrationsURLParameters) => { - const url = buildUrl({ - categoryId, - searchString, - subCategoryId, - }); - // Use .replace so the browser's back button is not tied to single keystroke - history.replace(url); - }; - const { data: eprPackages, isLoading: isLoadingAllPackages, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_build_integrations_url.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_build_integrations_url.tsx new file mode 100644 index 0000000000000..4c2a45586844d --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_build_integrations_url.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { useLocation, useParams, useHistory } from 'react-router-dom'; + +import { useStartServices } from '../../../../../hooks'; +import { useLink } from '../../../../../hooks'; + +import type { CategoryParams } from '..'; +import { getParams } from '..'; + +import { pagePathGetters } from '../../../../../constants'; + +export interface IntegrationsURLParameters { + searchString?: string; + categoryId?: string; + subCategoryId?: string; +} + +export const useBuildIntegrationsUrl = () => { + const { http } = useStartServices(); + const addBasePath = http.basePath.prepend; + + const { + selectedCategory: initialSelectedCategory, + selectedSubcategory: initialSubcategory, + searchParam, + } = getParams(useParams(), useLocation().search); + + const { getHref, getAbsolutePath } = useLink(); + const history = useHistory(); + + const buildUrl = ({ searchString, categoryId, subCategoryId }: IntegrationsURLParameters) => { + const url = pagePathGetters.integrations_all({ + category: categoryId ? categoryId : '', + subCategory: subCategoryId ? subCategoryId : '', + searchTerm: searchString ? searchString : '', + })[1]; + return url; + }; + + const setUrlandPushHistory = ({ + searchString, + categoryId, + subCategoryId, + }: IntegrationsURLParameters) => { + const url = buildUrl({ + categoryId, + searchString, + subCategoryId, + }); + history.push(url); + }; + + const setUrlandReplaceHistory = ({ + searchString, + categoryId, + subCategoryId, + }: IntegrationsURLParameters) => { + const url = buildUrl({ + categoryId, + searchString, + subCategoryId, + }); + // Use .replace so the browser's back button is not tied to single keystroke + history.replace(url); + }; + + return { + initialSelectedCategory, + initialSubcategory, + setUrlandPushHistory, + setUrlandReplaceHistory, + getHref, + getAbsolutePath, + searchParam, + addBasePath, + }; +}; diff --git a/x-pack/plugins/fleet/public/types/ui_extensions.ts b/x-pack/plugins/fleet/public/types/ui_extensions.ts index fc8726e7b813a..53ae5322f0d9d 100644 --- a/x-pack/plugins/fleet/public/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/types/ui_extensions.ts @@ -10,7 +10,9 @@ import type { ComponentType, LazyExoticComponent } from 'react'; import type { FleetServerAgentComponentUnit } from '../../common/types/models/agent'; -import type { Agent, NewPackagePolicy, PackageInfo, PackagePolicy } from '.'; +import type { PackagePolicyValidationResults } from '../services'; + +import type { Agent, AgentPolicy, NewPackagePolicy, PackageInfo, PackagePolicy } from '.'; /** Register a Fleet UI extension */ export type UIExtensionRegistrationCallback = (extensionPoint: UIExtensionPoint) => void; @@ -20,6 +22,22 @@ export interface UIExtensionsStorage { [key: string]: Partial>; } +/** + * UI Component Extension is used to replace the Define Step on + * the pages displaying the ability to edit/create an Integration Policy + */ +export type PackagePolicyReplaceDefineStepExtensionComponent = + ComponentType; + +export type PackagePolicyReplaceDefineStepExtensionComponentProps = ( + | (PackagePolicyEditExtensionComponentProps & { isEditPage: true }) + | (PackagePolicyCreateExtensionComponentProps & { isEditPage: false }) +) & { + validationResults?: PackagePolicyValidationResults; + agentPolicy?: AgentPolicy; + packageInfo: PackageInfo; +}; + /** * UI Component Extension is used on the pages displaying the ability to edit an * Integration Policy @@ -73,6 +91,12 @@ export interface PackageGenericErrorsListProps { packageErrors: FleetServerAgentComponentUnit[]; } +export interface PackagePolicyReplaceDefineStepExtension { + package: string; + view: 'package-policy-replace-define-step'; + Component: LazyExoticComponent; +} + /** Extension point registration contract for Integration Policy Edit views */ export interface PackagePolicyEditExtension { package: string; @@ -184,6 +208,7 @@ export interface AgentEnrollmentFlyoutFinalStepExtension { /** Fleet UI Extension Point */ export type UIExtensionPoint = + | PackagePolicyReplaceDefineStepExtension | PackagePolicyEditExtension | PackagePolicyResponseExtension | PackagePolicyEditTabsExtension diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index bb43d32b6cfaf..24dd0d355411d 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -186,10 +186,10 @@ export const getAgentsHandler: RequestHandler< kuery: request.query.kuery, sortField: request.query.sortField, sortOrder: request.query.sortOrder, - getTotalInactive: request.query.showInactive, + getStatusSummary: request.query.getStatusSummary, }); - const { total, page, perPage, totalInactive = 0 } = agentRes; + const { total, page, perPage, statusSummary } = agentRes; let { agents } = agentRes; // Assign metrics @@ -201,9 +201,9 @@ export const getAgentsHandler: RequestHandler< list: agents, // deprecated items: agents, total, - totalInactive, page, perPage, + ...(statusSummary ? { statusSummary } : {}), }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts index 04dce51e52969..92ab414eab2b1 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.test.ts @@ -14,16 +14,8 @@ import type { FleetAuthzRouter } from '../../services/security'; import { PACKAGE_POLICY_API_ROUTES } from '../../../common/constants'; import { appContextService, packagePolicyService } from '../../services'; import { createAppContextStartContractMock, xpackMocks } from '../../mocks'; -import type { - PackagePolicyClient, - PostPackagePolicyCreateCallback, - PutPackagePolicyUpdateCallback, - FleetRequestHandlerContext, -} from '../..'; -import type { - CreatePackagePolicyRequestSchema, - UpdatePackagePolicyRequestSchema, -} from '../../types/rest_spec'; +import type { PackagePolicyClient, FleetRequestHandlerContext } from '../..'; +import type { UpdatePackagePolicyRequestSchema } from '../../types/rest_spec'; import type { FleetRequestHandler } from '../../types'; import type { PackagePolicy } from '../../types'; @@ -125,7 +117,6 @@ describe('When calling package policy', () => { let routeConfig: RouteConfig; let context: FleetRequestHandlerContext; let response: ReturnType; - let packagePolicyServiceWithAuthzMock: jest.Mocked; beforeEach(() => { routerMock = httpServiceMock.createRouter() as unknown as jest.Mocked; @@ -135,8 +126,7 @@ describe('When calling package policy', () => { beforeEach(async () => { appContextService.start(createAppContextStartContractMock()); context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext; - packagePolicyServiceWithAuthzMock = (await context.fleet).packagePolicyService - .asCurrentUser as jest.Mocked; + (await context.fleet).packagePolicyService.asCurrentUser as jest.Mocked; response = httpServerMock.createResponseFactory(); }); @@ -145,186 +135,6 @@ describe('When calling package policy', () => { appContextService.stop(); }); - describe('create api handler', () => { - const getCreateKibanaRequest = ( - newData?: typeof CreatePackagePolicyRequestSchema.body - ): KibanaRequest => { - return httpServerMock.createKibanaRequest< - undefined, - undefined, - typeof CreatePackagePolicyRequestSchema.body - >({ - path: routeConfig.path, - method: 'post', - body: newData || { - name: 'endpoint-1', - description: '', - policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c', - enabled: true, - inputs: [], - namespace: 'default', - package: { name: 'endpoint', title: 'Elastic Endpoint', version: '0.5.0' }, - }, - }); - }; - - // Set the routeConfig and routeHandler to the Create API - beforeEach(() => { - [routeConfig, routeHandler] = routerMock.post.mock.calls.find( - ([{ path }]) => path === PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN - )!; - }); - - describe('and external callbacks are registered', () => { - const callbackCallingOrder: string[] = []; - - // Callback one adds an input that includes a `config` property - const callbackOne: PostPackagePolicyCreateCallback | PutPackagePolicyUpdateCallback = jest.fn( - async (ds) => { - callbackCallingOrder.push('one'); - const newDs = { - ...ds, - inputs: [ - { - type: 'endpoint', - enabled: true, - streams: [], - config: { - one: { - value: 'inserted by callbackOne', - }, - }, - }, - ], - }; - return newDs; - } - ); - - // Callback two adds an additional `input[0].config` property - const callbackTwo: PostPackagePolicyCreateCallback | PutPackagePolicyUpdateCallback = jest.fn( - async (ds) => { - callbackCallingOrder.push('two'); - const newDs = { - ...ds, - inputs: [ - { - ...ds.inputs[0], - config: { - ...ds.inputs[0].config, - two: { - value: 'inserted by callbackTwo', - }, - }, - }, - ], - }; - return newDs; - } - ); - - beforeEach(() => { - appContextService.addExternalCallback('packagePolicyCreate', callbackOne); - appContextService.addExternalCallback('packagePolicyCreate', callbackTwo); - }); - - afterEach(() => (callbackCallingOrder.length = 0)); - - it('should create with data from callback', async () => { - const request = getCreateKibanaRequest(); - packagePolicyServiceMock.runExternalCallbacks.mockImplementationOnce(() => - Promise.resolve({ - policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c', - description: '', - enabled: true, - inputs: [ - { - config: { - one: { - value: 'inserted by callbackOne', - }, - two: { - value: 'inserted by callbackTwo', - }, - }, - enabled: true, - streams: [], - type: 'endpoint', - }, - ], - name: 'endpoint-1', - namespace: 'default', - package: { - name: 'endpoint', - title: 'Elastic Endpoint', - version: '0.5.0', - }, - }) - ); - await routeHandler(context, request, response); - expect(response.ok).toHaveBeenCalled(); - - expect(packagePolicyServiceWithAuthzMock.create.mock.calls[0][2]).toEqual({ - policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c', - description: '', - enabled: true, - inputs: [ - { - config: { - one: { - value: 'inserted by callbackOne', - }, - two: { - value: 'inserted by callbackTwo', - }, - }, - enabled: true, - streams: [], - type: 'endpoint', - }, - ], - name: 'endpoint-1', - namespace: 'default', - package: { - name: 'endpoint', - title: 'Elastic Endpoint', - version: '0.5.0', - }, - }); - }); - }); - - describe('postCreate callback registration', () => { - it('should call to packagePolicyCreate and packagePolicyPostCreate call backs', async () => { - const request = getCreateKibanaRequest(); - await routeHandler(context, request, response); - - expect(response.ok).toHaveBeenCalled(); - expect(packagePolicyService.runExternalCallbacks).toBeCalledTimes(2); - - const firstCB = packagePolicyServiceMock.runExternalCallbacks.mock.calls[0][0]; - const secondCB = packagePolicyServiceMock.runExternalCallbacks.mock.calls[1][0]; - - expect(firstCB).toEqual('packagePolicyCreate'); - expect(secondCB).toEqual('packagePolicyPostCreate'); - }); - - it('should not call packagePolicyPostCreate call back in case of packagePolicy create failed', async () => { - const request = getCreateKibanaRequest(); - - packagePolicyServiceWithAuthzMock.create.mockImplementationOnce(() => { - throw new Error('foo'); - }); - - await routeHandler(context, request, response); - const firstCB = packagePolicyServiceMock.runExternalCallbacks.mock.calls[0][0]; - - expect(firstCB).toEqual('packagePolicyCreate'); - expect(packagePolicyService.runExternalCallbacks).toBeCalledTimes(1); - }); - }); - }); - describe('update api handler', () => { const getUpdateKibanaRequest = ( newData?: typeof UpdatePackagePolicyRequestSchema.body diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 8735cdf08cafd..51b4056843d25 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -247,33 +247,21 @@ export const createPackagePolicyHandler: FleetRequestHandler< } as NewPackagePolicy); } - const newData = await packagePolicyService.runExternalCallbacks( - 'packagePolicyCreate', - newPackagePolicy, - context, - request - ); - // Create package policy const packagePolicy = await fleetContext.packagePolicyService.asCurrentUser.create( soClient, esClient, - newData, + newPackagePolicy, { user, force, spaceId, - } - ); - - const enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks( - 'packagePolicyPostCreate', - packagePolicy, + }, context, request ); - const body: CreatePackagePolicyResponse = { item: enrichedPackagePolicy }; + const body: CreatePackagePolicyResponse = { item: packagePolicy }; return response.ok({ body, @@ -368,12 +356,6 @@ export const updatePackagePolicyHandler: FleetRequestHandler< vars: body.vars ?? packagePolicy.vars, } as NewPackagePolicy; } - newData = await packagePolicyService.runExternalCallbacks( - 'packagePolicyUpdate', - newData, - context, - request - ); const updatedPackagePolicy = await packagePolicyService.update( soClient, @@ -400,46 +382,17 @@ export const deletePackagePolicyHandler: RequestHandler< const soClient = coreContext.savedObjects.client; const esClient = coreContext.elasticsearch.client.asInternalUser; const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; - const logger = appContextService.getLogger(); try { - try { - const packagePolicies = await packagePolicyService.getByIDs( - soClient, - request.body.packagePolicyIds, - { ignoreMissing: true } - ); - - if (packagePolicies) { - await packagePolicyService.runExternalCallbacks( - 'packagePolicyDelete', - packagePolicies, - context, - request - ); - } - } catch (error) { - logger.error(`An error occurred executing external callback: ${error}`); - logger.error(error); - } - const body: PostDeletePackagePoliciesResponse = await packagePolicyService.delete( soClient, esClient, request.body.packagePolicyIds, - { user, force: request.body.force, skipUnassignFromAgentPolicies: request.body.force } + { user, force: request.body.force, skipUnassignFromAgentPolicies: request.body.force }, + context, + request ); - try { - await packagePolicyService.runExternalCallbacks( - 'packagePolicyPostDelete', - body, - context, - request - ); - } catch (error) { - logger.error(`An error occurred executing external callback: ${error}`); - logger.error(error); - } + return response.ok({ body, }); @@ -457,30 +410,15 @@ export const deleteOnePackagePolicyHandler: RequestHandler< const soClient = coreContext.savedObjects.client; const esClient = coreContext.elasticsearch.client.asInternalUser; const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; - const logger = appContextService.getLogger(); try { - try { - const packagePolicy = await packagePolicyService.get( - soClient, - request.params.packagePolicyId - ); - await packagePolicyService.runExternalCallbacks( - 'packagePolicyDelete', - packagePolicy ? [packagePolicy] : [], - context, - request - ); - } catch (error) { - logger.error(`An error occurred executing external callback: ${error}`); - logger.error(error); - } - const res = await packagePolicyService.delete( soClient, esClient, [request.params.packagePolicyId], - { user, force: request.query.force, skipUnassignFromAgentPolicies: request.query.force } + { user, force: request.query.force, skipUnassignFromAgentPolicies: request.query.force }, + context, + request ); if ( @@ -493,17 +431,7 @@ export const deleteOnePackagePolicyHandler: RequestHandler< body: res[0].body, }); } - try { - await packagePolicyService.runExternalCallbacks( - 'packagePolicyPostDelete', - res, - context, - request - ); - } catch (error) { - logger.error(`An error occurred executing external callback: ${error}`); - logger.error(error); - } + return response.ok({ body: { id: request.params.packagePolicyId }, }); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap index 3917a7d71533b..49a11c226ce49 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap @@ -109,6 +109,7 @@ Object { "logs-elastic_agent.elastic_agent-testnamespace123", "logs-elastic_agent.apm_server-testnamespace123", "logs-elastic_agent.filebeat-testnamespace123", + "logs-elastic_agent.filebeat_input-testnamespace123", "logs-elastic_agent.fleet_server-testnamespace123", "logs-elastic_agent.metricbeat-testnamespace123", "logs-elastic_agent.osquerybeat-testnamespace123", @@ -122,6 +123,7 @@ Object { "metrics-elastic_agent.elastic_agent-testnamespace123", "metrics-elastic_agent.apm_server-testnamespace123", "metrics-elastic_agent.filebeat-testnamespace123", + "metrics-elastic_agent.filebeat_input-testnamespace123", "metrics-elastic_agent.fleet_server-testnamespace123", "metrics-elastic_agent.metricbeat-testnamespace123", "metrics-elastic_agent.osquerybeat-testnamespace123", @@ -152,6 +154,7 @@ Object { "logs-elastic_agent.elastic_agent-testnamespace123", "logs-elastic_agent.apm_server-testnamespace123", "logs-elastic_agent.filebeat-testnamespace123", + "logs-elastic_agent.filebeat_input-testnamespace123", "logs-elastic_agent.fleet_server-testnamespace123", "logs-elastic_agent.metricbeat-testnamespace123", "logs-elastic_agent.osquerybeat-testnamespace123", @@ -182,6 +185,7 @@ Object { "metrics-elastic_agent.elastic_agent-testnamespace123", "metrics-elastic_agent.apm_server-testnamespace123", "metrics-elastic_agent.filebeat-testnamespace123", + "metrics-elastic_agent.filebeat_input-testnamespace123", "metrics-elastic_agent.fleet_server-testnamespace123", "metrics-elastic_agent.metricbeat-testnamespace123", "metrics-elastic_agent.osquerybeat-testnamespace123", diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index 014764743ee35..b223e88102d9c 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -169,13 +169,6 @@ describe('agent policy', () => { ]); }); - it('should run package policy delete external callbacks', async () => { - await agentPolicyService.delete(soClient, esClient, 'mocked'); - expect(packagePolicyService.runPostDeleteExternalCallbacks).toHaveBeenCalledWith([ - { id: 'package-1' }, - ]); - }); - it('should throw error for agent policy which has managed package poolicy', async () => { mockedPackagePolicyService.findAllForAgentPolicy.mockReturnValue([ { @@ -192,12 +185,6 @@ describe('agent policy', () => { ).message ); } - - await agentPolicyService.delete(soClient, esClient, 'mocked', { force: true }); - - expect(packagePolicyService.runPostDeleteExternalCallbacks).toHaveBeenCalledWith([ - { id: 'package-1' }, - ]); }); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index f2e51b9c95bf5..d845d466ea381 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -51,7 +51,6 @@ import type { FleetServerPolicy, Installation, Output, - PostDeletePackagePoliciesResponse, PackageInfo, } from '../../common/types'; import { @@ -681,25 +680,15 @@ class AgentPolicyService { ); } - await packagePolicyService.runDeleteExternalCallbacks(packagePolicies); - - const deletedPackagePolicies: PostDeletePackagePoliciesResponse = - await packagePolicyService.delete( - soClient, - esClient, - packagePolicies.map((p) => p.id), - { - force: options?.force, - skipUnassignFromAgentPolicies: true, - } - ); - try { - await packagePolicyService.runPostDeleteExternalCallbacks(deletedPackagePolicies); - } catch (error) { - const logger = appContextService.getLogger(); - logger.error(`An error occurred executing external callback: ${error}`); - logger.error(error); - } + await packagePolicyService.delete( + soClient, + esClient, + packagePolicies.map((p) => p.id), + { + force: options?.force, + skipUnassignFromAgentPolicies: true, + } + ); } if (agentPolicy.is_preconfigured && !options?.force) { diff --git a/x-pack/plugins/fleet/server/services/agents/action_status.ts b/x-pack/plugins/fleet/server/services/agents/action_status.ts index 251da3eeac7b5..24bb3e3b5b6b4 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_status.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_status.ts @@ -119,14 +119,13 @@ export async function getActionStatuses( ...action, nbAgentsAck: nbAgentsAck - errorCount, nbAgentsFailed: errorCount, - status: - errorCount > 0 - ? 'FAILED' - : complete - ? 'COMPLETE' - : cancelledAction - ? 'CANCELLED' - : action.status, + status: cancelledAction + ? 'CANCELLED' + : errorCount > 0 && complete + ? 'FAILED' + : complete + ? 'COMPLETE' + : action.status, nbAgentsActioned, cancellationTime: cancelledAction?.timestamp, completionTime, @@ -196,7 +195,10 @@ async function _getActions( const source = hit._source!; if (!acc[source.action_id!]) { - const isExpired = source.expiration ? Date.parse(source.expiration) < Date.now() : false; + const isExpired = + source.expiration && source.type !== 'UPGRADE' + ? Date.parse(source.expiration) < Date.now() + : false; acc[hit._source.action_id] = { actionId: hit._source.action_id, nbAgentsActionCreated: 0, diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index 76fcaac7086d5..80a23e4ecbb7b 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -261,6 +261,7 @@ export async function cancelAgentAction(esClient: ElasticsearchClient, actionId: continue; } if (hit._source.type === 'UPGRADE') { + const errors = {}; await bulkUpdateAgents( esClient, hit._source.agents.map((agentId) => ({ @@ -270,8 +271,13 @@ export async function cancelAgentAction(esClient: ElasticsearchClient, actionId: upgrade_started_at: null, }, })), - {} + errors ); + if (Object.keys(errors).length > 0) { + appContextService + .getLogger() + .debug(`Errors while bulk updating agents for cancel action: ${JSON.stringify(errors)}`); + } } await createAgentAction(esClient, { id: cancelActionId, diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 412603df68c81..379a656b7d519 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -13,7 +13,7 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; import { appContextService, agentPolicyService } from '..'; -import type { FleetServerAgent } from '../../../common/types'; +import type { AgentStatus, FleetServerAgent } from '../../../common/types'; import { SO_SEARCH_LIMIT } from '../../../common/constants'; import { isAgentUpgradeable } from '../../../common/services'; import { AGENTS_INDEX } from '../../constants'; @@ -194,7 +194,7 @@ export async function getAgentsByKuery( soClient: SavedObjectsClientContract, options: ListWithKuery & { showInactive: boolean; - getTotalInactive?: boolean; + getStatusSummary?: boolean; sortField?: string; sortOrder?: 'asc' | 'desc'; pitId?: string; @@ -205,7 +205,7 @@ export async function getAgentsByKuery( total: number; page: number; perPage: number; - totalInactive?: number; + statusSummary?: Record; }> { const { page = 1, @@ -214,10 +214,10 @@ export async function getAgentsByKuery( sortOrder = options.sortOrder ?? 'desc', kuery, showInactive = false, + getStatusSummary = false, showUpgradeable, searchAfter, pitId, - getTotalInactive = false, } = options; const filters = []; @@ -238,8 +238,24 @@ export async function getAgentsByKuery( const secondarySort: estypes.Sort = isDefaultSort ? [{ 'local_metadata.host.hostname.keyword': { order: 'asc' } }] : []; + + const statusSummary: Record = { + online: 0, + error: 0, + inactive: 0, + offline: 0, + updating: 0, + unenrolled: 0, + degraded: 0, + enrolling: 0, + unenrolling: 0, + }; + const queryAgents = async (from: number, size: number) => - esClient.search({ + esClient.search< + FleetServerAgent, + { status: { buckets: Array<{ key: AgentStatus; doc_count: number }> } } + >({ from, size, track_total_hits: true, @@ -247,7 +263,7 @@ export async function getAgentsByKuery( runtime_mappings: runtimeFields, fields: Object.keys(runtimeFields), sort: [{ [sortField]: { order: sortOrder } }, ...secondarySort], - post_filter: kueryNode ? toElasticsearchQuery(kueryNode) : undefined, + query: kueryNode ? toElasticsearchQuery(kueryNode) : undefined, ...(pitId ? { pit: { @@ -260,13 +276,7 @@ export async function getAgentsByKuery( ignore_unavailable: true, }), ...(pitId && searchAfter ? { search_after: searchAfter, from: 0 } : {}), - ...(getTotalInactive && { - aggregations: { - totalInactive: { - filter: { bool: { must: { terms: { status: ['inactive', 'unenrolled'] } } } }, - }, - }, - }), + ...(getStatusSummary && { aggs: { status: { terms: { field: 'status' } } } }), }); let res; try { @@ -278,10 +288,6 @@ export async function getAgentsByKuery( let agents = res.hits.hits.map(searchHitToAgent); let total = res.hits.total as number; - let totalInactive = 0; - if (getTotalInactive && res.aggregations) { - totalInactive = res.aggregations?.totalInactive?.doc_count ?? 0; - } // filtering for a range on the version string will not work, // nor does filtering on a flattened field (local_metadata), so filter here if (showUpgradeable) { @@ -303,12 +309,18 @@ export async function getAgentsByKuery( } } + if (getStatusSummary) { + res.aggregations?.status.buckets.forEach((bucket) => { + statusSummary[bucket.key] = bucket.doc_count; + }); + } + return { agents, total, page, perPage, - ...(getTotalInactive && { totalInactive }), + ...(getStatusSummary ? { statusSummary } : {}), }; } @@ -477,7 +489,7 @@ export async function bulkUpdateAgents( { update: { _id: agentId, - retry_on_conflict: 3, + retry_on_conflict: 5, }, }, { diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index fecc369f6ad33..88761c53ee473 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -16,6 +16,8 @@ import type { QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/types'; +import { agentStatusesToSummary } from '../../../common/services'; + import { AGENTS_INDEX } from '../../constants'; import type { AgentStatus } from '../../types'; import { FleetUnauthorizedError } from '../../errors'; @@ -122,15 +124,8 @@ export async function getAgentStatusForAgentPolicy( } }); - const combinedStatuses = { - online: statuses.online, - error: statuses.error + statuses.degraded, - inactive: statuses.inactive, - offline: statuses.offline, - updating: statuses.updating + statuses.enrolling + statuses.unenrolling, - unenrolled: statuses.unenrolled, - }; - + const { healthy: online, unhealthy: error, ...otherStatuses } = agentStatusesToSummary(statuses); + const combinedStatuses = { online, error, ...otherStatuses }; return { ...combinedStatuses, /* @deprecated no agents will have other status */ diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index ae50ec3f785f3..343b6129d0a1a 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -826,6 +826,83 @@ describe('Package policy service', () => { expect(result.name).toEqual('endpoint-1'); }); + it('should not fail to update if skipUniqueNameVerification: false when the name is not updated but duplicates exists', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + total: 1, + per_page: 1, + page: 1, + saved_objects: [ + { + id: 'existing-package-policy', + type: 'ingest-package-policies', + score: 1, + references: [], + version: '1.0.0', + attributes: { + name: 'endpoint-1', + description: '', + namespace: 'default', + enabled: true, + policy_id: 'policy-id-1', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.9.0', + }, + inputs: [], + }, + }, + ], + }); + savedObjectsClient.get.mockResolvedValue({ + id: 'the-package-policy-id', + type: 'abcd', + references: [], + version: 'test', + attributes: { + name: 'endpoint-1', + }, + }); + savedObjectsClient.update.mockImplementation( + async ( + type: string, + id: string, + attrs: any + ): Promise> => { + savedObjectsClient.get.mockResolvedValue({ + id: 'the-package-policy-id', + type, + references: [], + version: 'test', + attributes: attrs, + }); + return attrs; + } + ); + const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const result = await packagePolicyService.update( + savedObjectsClient, + elasticsearchClient, + 'the-package-policy-id', + { + name: 'endpoint-1', + description: '', + namespace: 'default', + enabled: true, + policy_id: '93c46720-c217-11ea-9906-b5b8a21b268e', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '0.9.0', + }, + inputs: [], + }, + { skipUniqueNameVerification: false } + ); + expect(result.name).toEqual('endpoint-1'); + }); + it('should throw if the user try to update input vars that are frozen', async () => { const savedObjectsClient = savedObjectsClientMock.create(); const mockPackagePolicy = createPackagePolicyMock(); @@ -2055,10 +2132,10 @@ describe('Package policy service', () => { { id: 'a', success: true }, { id: 'a', success: true }, ]; - callbackOne = jest.fn(async (deletedPolicies) => { + callbackOne = jest.fn(async (deletedPolicies, soClient, esClient) => { callingOrder.push('one'); }); - callbackTwo = jest.fn(async (deletedPolicies) => { + callbackTwo = jest.fn(async (deletedPolicies, soClient, esClient) => { callingOrder.push('two'); }); appContextService.addExternalCallback('packagePolicyPostDelete', callbackOne); @@ -2070,25 +2147,54 @@ describe('Package policy service', () => { }); it('should execute external callbacks', async () => { - await packagePolicyService.runPostDeleteExternalCallbacks(deletedPackagePolicies); + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - expect(callbackOne).toHaveBeenCalledWith(deletedPackagePolicies); - expect(callbackTwo).toHaveBeenCalledWith(deletedPackagePolicies); + await packagePolicyService.runPostDeleteExternalCallbacks( + deletedPackagePolicies, + soClient, + esClient + ); + + expect(callbackOne).toHaveBeenCalledWith( + deletedPackagePolicies, + expect.any(Object), + expect.any(Object), + undefined, + undefined + ); + expect(callbackTwo).toHaveBeenCalledWith( + deletedPackagePolicies, + expect.any(Object), + expect.any(Object), + undefined, + undefined + ); expect(callingOrder).toEqual(['one', 'two']); }); it("should execute all external callbacks even if one throw's", async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + callbackOne.mockImplementation(async (deletedPolicies) => { callingOrder.push('one'); throw new Error('foo'); }); await expect( - packagePolicyService.runPostDeleteExternalCallbacks(deletedPackagePolicies) + packagePolicyService.runPostDeleteExternalCallbacks( + deletedPackagePolicies, + soClient, + esClient + ) ).rejects.toThrow(FleetError); expect(callingOrder).toEqual(['one', 'two']); }); it('should provide an array of errors encountered by running external callbacks', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + let error: FleetError; const callbackOneError = new Error('foo 1'); const callbackTwoError = new Error('foo 2'); @@ -2103,7 +2209,7 @@ describe('Package policy service', () => { }); await packagePolicyService - .runPostDeleteExternalCallbacks(deletedPackagePolicies) + .runPostDeleteExternalCallbacks(deletedPackagePolicies, soClient, esClient) .catch((e) => { error = e; }); @@ -2126,10 +2232,10 @@ describe('Package policy service', () => { appContextService.start(createAppContextStartContractMock()); callingOrder = []; packagePolicies = [{ id: 'a' }, { id: 'a' }] as DeletePackagePoliciesResponse; - callbackOne = jest.fn(async (deletedPolicies) => { + callbackOne = jest.fn(async (deletedPolicies, soClient, esClient) => { callingOrder.push('one'); }); - callbackTwo = jest.fn(async (deletedPolicies) => { + callbackTwo = jest.fn(async (deletedPolicies, soClient, esClient) => { callingOrder.push('two'); }); appContextService.addExternalCallback('packagePolicyDelete', callbackOne); @@ -2141,25 +2247,31 @@ describe('Package policy service', () => { }); it('should execute external callbacks', async () => { - await packagePolicyService.runDeleteExternalCallbacks(packagePolicies); + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + await packagePolicyService.runDeleteExternalCallbacks(packagePolicies, soClient, esClient); - expect(callbackOne).toHaveBeenCalledWith(packagePolicies); - expect(callbackTwo).toHaveBeenCalledWith(packagePolicies); + expect(callbackOne).toHaveBeenCalledWith(packagePolicies, soClient, esClient); + expect(callbackTwo).toHaveBeenCalledWith(packagePolicies, soClient, esClient); expect(callingOrder).toEqual(['one', 'two']); }); it("should execute all external callbacks even if one throw's", async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; callbackOne.mockImplementation(async (deletedPolicies) => { callingOrder.push('one'); throw new Error('foo'); }); await expect( - packagePolicyService.runDeleteExternalCallbacks(packagePolicies) + packagePolicyService.runDeleteExternalCallbacks(packagePolicies, soClient, esClient) ).rejects.toThrow(FleetError); expect(callingOrder).toEqual(['one', 'two']); }); it('should provide an array of errors encountered by running external callbacks', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; let error: FleetError; const callbackOneError = new Error('foo 1'); const callbackTwoError = new Error('foo 2'); @@ -2173,9 +2285,11 @@ describe('Package policy service', () => { throw callbackTwoError; }); - await packagePolicyService.runDeleteExternalCallbacks(packagePolicies).catch((e) => { - error = e; - }); + await packagePolicyService + .runDeleteExternalCallbacks(packagePolicies, soClient, esClient) + .catch((e) => { + error = e; + }); expect(error!.message).toEqual( '2 encountered while executing package delete external callbacks' @@ -2257,6 +2371,9 @@ describe('Package policy service', () => { }); it('should call external callbacks in expected order', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const callbackA: CombinedExternalCallback = jest.fn(async (ds) => { callbackCallingOrder.push('a'); return ds; @@ -2273,6 +2390,8 @@ describe('Package policy service', () => { await packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', newPackagePolicy, + soClient, + esClient, coreMock.createCustomRequestHandlerContext(context), request ); @@ -2280,12 +2399,17 @@ describe('Package policy service', () => { }); it('should feed package policy returned by last callback', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + appContextService.addExternalCallback('packagePolicyCreate', callbackOne); appContextService.addExternalCallback('packagePolicyCreate', callbackTwo); await packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', newPackagePolicy, + soClient, + esClient, coreMock.createCustomRequestHandlerContext(context), request ); @@ -2329,10 +2453,15 @@ describe('Package policy service', () => { }); it('should fail to execute remaining callbacks after a callback exception', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + try { await packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', newPackagePolicy, + soClient, + esClient, coreMock.createCustomRequestHandlerContext(context), request ); @@ -2348,10 +2477,14 @@ describe('Package policy service', () => { }); it('should fail to return the package policy', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; expect( packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', newPackagePolicy, + soClient, + esClient, coreMock.createCustomRequestHandlerContext(context), request ) @@ -2427,6 +2560,9 @@ describe('Package policy service', () => { }); it('should execute PostPackagePolicyPostCreateCallback external callbacks', async () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const callbackA: PostPackagePolicyPostCreateCallback = jest.fn(async (ds) => { callbackCallingOrder.push('a'); return ds; @@ -2444,12 +2580,26 @@ describe('Package policy service', () => { await packagePolicyService.runExternalCallbacks( 'packagePolicyPostCreate', packagePolicy, + soClient, + esClient, requestContext, request ); - expect(callbackA).toHaveBeenCalledWith(packagePolicy, requestContext, request); - expect(callbackB).toHaveBeenCalledWith(packagePolicy, requestContext, request); + expect(callbackA).toHaveBeenCalledWith( + packagePolicy, + soClient, + esClient, + requestContext, + request + ); + expect(callbackB).toHaveBeenCalledWith( + packagePolicy, + soClient, + esClient, + requestContext, + request + ); expect(callbackCallingOrder).toEqual(['a', 'b']); }); }); diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index c7152e2d26a3e..c665d9a6f6f97 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -13,9 +13,9 @@ import { getFlattenedObject } from '@kbn/std'; import type { KibanaRequest, ElasticsearchClient, - RequestHandlerContext, SavedObjectsClientContract, Logger, + RequestHandlerContext, } from '@kbn/core/server'; import { v4 as uuidv4 } from 'uuid'; import { safeLoad } from 'js-yaml'; @@ -133,31 +133,47 @@ class PackagePolicyClientImpl implements PackagePolicyClient { skipUniqueNameVerification?: boolean; overwrite?: boolean; packageInfo?: PackageInfo; - } + }, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise { const logger = appContextService.getLogger(); - const agentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id, true); - if (agentPolicy && packagePolicy.package?.name === FLEET_APM_PACKAGE) { + const enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks( + 'packagePolicyCreate', + packagePolicy, + soClient, + esClient, + context, + request + ); + + const agentPolicy = await agentPolicyService.get( + soClient, + enrichedPackagePolicy.policy_id, + true + ); + + if (agentPolicy && enrichedPackagePolicy.package?.name === FLEET_APM_PACKAGE) { const dataOutput = await getDataOutputForAgentPolicy(soClient, agentPolicy); if (dataOutput.type === outputType.Logstash) { throw new FleetError('You cannot add APM to a policy using a logstash output'); } } - await validateIsNotHostedPolicy(soClient, packagePolicy.policy_id, options?.force); + await validateIsNotHostedPolicy(soClient, enrichedPackagePolicy.policy_id, options?.force); // trailing whitespace causes issues creating API keys - packagePolicy.name = packagePolicy.name.trim(); + enrichedPackagePolicy.name = enrichedPackagePolicy.name.trim(); if (!options?.skipUniqueNameVerification) { const existingPoliciesWithName = await this.list(soClient, { perPage: 1, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${packagePolicy.name}"`, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${enrichedPackagePolicy.name}"`, }); // Check that the name does not exist already if (existingPoliciesWithName.items.length > 0) { throw new FleetError( - `An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.` + `An integration policy with the name ${enrichedPackagePolicy.name} already exists. Please rename it or choose a different name.` ); } } @@ -165,32 +181,36 @@ class PackagePolicyClientImpl implements PackagePolicyClient { let elasticsearchPrivileges: NonNullable['privileges']; // Add ids to stream const packagePolicyId = options?.id || uuidv4(); - let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) => + let inputs: PackagePolicyInput[] = enrichedPackagePolicy.inputs.map((input) => assignStreamIdToInput(packagePolicyId, input) ); // Make sure the associated package is installed - if (packagePolicy.package?.name) { + if (enrichedPackagePolicy.package?.name) { if (!options?.skipEnsureInstalled) { await ensureInstalledPackage({ esClient, spaceId: options?.spaceId || DEFAULT_SPACE_ID, savedObjectsClient: soClient, - pkgName: packagePolicy.package.name, - pkgVersion: packagePolicy.package.version, + pkgName: enrichedPackagePolicy.package.name, + pkgVersion: enrichedPackagePolicy.package.version, force: options?.force, }); } // Handle component template/mappings updates for experimental features, e.g. synthetic source - await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy }); + await handleExperimentalDatastreamFeatureOptIn({ + soClient, + esClient, + packagePolicy: enrichedPackagePolicy, + }); const pkgInfo = options?.packageInfo ?? (await getPackageInfo({ savedObjectsClient: soClient, - pkgName: packagePolicy.package.name, - pkgVersion: packagePolicy.package.version, + pkgName: enrichedPackagePolicy.package.name, + pkgVersion: enrichedPackagePolicy.package.version, prerelease: true, })); @@ -203,9 +223,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ); } } - validatePackagePolicyOrThrow(packagePolicy, pkgInfo); + validatePackagePolicyOrThrow(enrichedPackagePolicy, pkgInfo); - inputs = await _compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs); + inputs = await _compilePackagePolicyInputs(pkgInfo, enrichedPackagePolicy.vars || {}, inputs); elasticsearchPrivileges = pkgInfo.elasticsearch?.privileges; @@ -214,7 +234,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { soClient, esClient, pkgInfo, - packagePolicy, + packagePolicy: enrichedPackagePolicy, force: !!options?.force, logger, }); @@ -225,9 +245,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { const newSo = await soClient.create( SAVED_OBJECT_TYPE, { - ...packagePolicy, - ...(packagePolicy.package - ? { package: omit(packagePolicy.package, 'experimental_data_stream_features') } + ...enrichedPackagePolicy, + ...(enrichedPackagePolicy.package + ? { package: omit(enrichedPackagePolicy.package, 'experimental_data_stream_features') } : {}), inputs, ...(elasticsearchPrivileges && { elasticsearch: { privileges: elasticsearchPrivileges } }), @@ -242,16 +262,18 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ); if (options?.bumpRevision ?? true) { - await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, { + await agentPolicyService.bumpRevision(soClient, esClient, enrichedPackagePolicy.policy_id, { user: options?.user, }); } - return { - id: newSo.id, - version: newSo.version, - ...newSo.attributes, - }; + const createdPackagePolicy = { id: newSo.id, version: newSo.version, ...newSo.attributes }; + return packagePolicyService.runExternalCallbacks( + 'packagePolicyPostCreate', + createdPackagePolicy, + soClient, + esClient + ); } public async bulkCreate( @@ -494,7 +516,23 @@ class PackagePolicyClientImpl implements PackagePolicyClient { options?: { user?: AuthenticatedUser; force?: boolean; skipUniqueNameVerification?: boolean }, currentVersion?: string ): Promise { - const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() }; + let enrichedPackagePolicy: UpdatePackagePolicy; + + try { + enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks( + 'packagePolicyUpdate', + packagePolicyUpdate, + soClient, + esClient + ); + } catch (error) { + const logger = appContextService.getLogger(); + logger.error(`An error occurred executing "packagePolicyUpdate" callback: ${error}`); + logger.error(error); + enrichedPackagePolicy = packagePolicyUpdate; + } + + const packagePolicy = { ...enrichedPackagePolicy, name: enrichedPackagePolicy.name.trim() }; const oldPackagePolicy = await this.get(soClient, id); const { version, ...restOfPackagePolicy } = packagePolicy; @@ -505,7 +543,11 @@ class PackagePolicyClientImpl implements PackagePolicyClient { throw new Error('Package policy not found'); } - if (!options?.skipUniqueNameVerification) { + if ( + packagePolicy.name && + packagePolicy.name !== oldPackagePolicy.name && + !options?.skipUniqueNameVerification + ) { // Check that the name does not exist already but exclude the current package policy const existingPoliciesWithName = await this.list(soClient, { perPage: SO_SEARCH_LIMIT, @@ -710,15 +752,35 @@ class PackagePolicyClientImpl implements PackagePolicyClient { soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, ids: string[], - options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean; force?: boolean } + options?: { + user?: AuthenticatedUser; + skipUnassignFromAgentPolicies?: boolean; + force?: boolean; + }, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise { const result: PostDeletePackagePoliciesResponse = []; + const logger = appContextService.getLogger(); const packagePolicies = await this.getByIDs(soClient, ids, { ignoreMissing: true }); if (!packagePolicies) { return []; } + try { + await packagePolicyService.runDeleteExternalCallbacks( + packagePolicies, + soClient, + esClient, + context, + request + ); + } catch (error) { + logger.error(`An error occurred executing "packagePolicyDelete" callback: ${error}`); + logger.error(error); + } + const uniqueAgentPolicyIds = [ ...new Set(packagePolicies.map((packagePolicy) => packagePolicy.policy_id)), ]; @@ -819,6 +881,19 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } } + try { + await packagePolicyService.runPostDeleteExternalCallbacks( + result, + soClient, + esClient, + context, + request + ); + } catch (error) { + logger.error(`An error occurred executing "packagePolicyPostDelete" callback: ${error}`); + logger.error(error); + } + return result; } @@ -1231,9 +1306,13 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ? PostDeletePackagePoliciesResponse : A extends 'packagePolicyPostCreate' ? PackagePolicy - : NewPackagePolicy, - context: RequestHandlerContext, - request: KibanaRequest + : A extends 'packagePolicyCreate' + ? NewPackagePolicy + : never, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise< A extends 'packagePolicyDelete' ? void @@ -1241,7 +1320,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { ? void : A extends 'packagePolicyPostCreate' ? PackagePolicy - : NewPackagePolicy + : A extends 'packagePolicyCreate' + ? NewPackagePolicy + : never >; public async runExternalCallbacks( externalCallbackType: ExternalCallback[0], @@ -1250,53 +1331,95 @@ class PackagePolicyClientImpl implements PackagePolicyClient { | NewPackagePolicy | PostDeletePackagePoliciesResponse | DeletePackagePoliciesResponse, - context: RequestHandlerContext, - request: KibanaRequest + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise { - if (externalCallbackType === 'packagePolicyPostDelete') { - return await this.runPostDeleteExternalCallbacks( - packagePolicy as PostDeletePackagePoliciesResponse - ); - } else if (externalCallbackType === 'packagePolicyDelete') { - return await this.runDeleteExternalCallbacks(packagePolicy as DeletePackagePoliciesResponse); - } else { - if (!Array.isArray(packagePolicy)) { - let newData = packagePolicy; - const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType); - if (externalCallbacks && externalCallbacks.size > 0) { - let updatedNewData = newData; - for (const callback of externalCallbacks) { - let result; - if (externalCallbackType === 'packagePolicyPostCreate') { - result = await (callback as PostPackagePolicyPostCreateCallback)( - updatedNewData as PackagePolicy, - context, - request - ); - updatedNewData = PackagePolicySchema.validate(result); - } else { - result = await (callback as PostPackagePolicyCreateCallback)( - updatedNewData as NewPackagePolicy, - context, - request - ); - } - if (externalCallbackType === 'packagePolicyCreate') { - updatedNewData = NewPackagePolicySchema.validate(result); - } else if (externalCallbackType === 'packagePolicyUpdate') { - updatedNewData = UpdatePackagePolicySchema.validate(result); + const logger = appContextService.getLogger(); + const numberOfCallbacks = appContextService.getExternalCallbacks(externalCallbackType)?.size; + logger.debug(`Running ${numberOfCallbacks} external callbacks for ${externalCallbackType}`); + try { + if (externalCallbackType === 'packagePolicyPostDelete') { + return await this.runPostDeleteExternalCallbacks( + packagePolicy as PostDeletePackagePoliciesResponse, + soClient, + esClient, + context, + request + ); + } else if (externalCallbackType === 'packagePolicyDelete') { + return await this.runDeleteExternalCallbacks( + packagePolicy as DeletePackagePoliciesResponse, + soClient, + esClient + ); + } else { + if (!Array.isArray(packagePolicy)) { + let newData = packagePolicy; + const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType); + if (externalCallbacks && externalCallbacks.size > 0) { + let updatedNewData = newData; + for (const callback of externalCallbacks) { + let result; + if (externalCallbackType === 'packagePolicyPostCreate') { + result = await (callback as PostPackagePolicyPostCreateCallback)( + updatedNewData as PackagePolicy, + soClient, + esClient, + context, + request + ); + updatedNewData = PackagePolicySchema.validate(result); + } else { + result = await (callback as PostPackagePolicyCreateCallback)( + updatedNewData as NewPackagePolicy, + soClient, + esClient, + context, + request + ); + } + + if (externalCallbackType === 'packagePolicyCreate') { + updatedNewData = NewPackagePolicySchema.validate(result); + } else if (externalCallbackType === 'packagePolicyUpdate') { + const omitted = { + ...omit(result, [ + 'id', + 'version', + 'revision', + 'updated_at', + 'updated_by', + 'created_at', + 'created_by', + 'elasticsearch', + ]), + inputs: result.inputs.map((input) => omit(input, ['compiled_input'])), + }; + + updatedNewData = UpdatePackagePolicySchema.validate(omitted); + } } - } - newData = updatedNewData; + newData = updatedNewData; + } + return newData; } - return newData; } + } catch (error) { + logger.error(`Error running external callbacks for ${externalCallbackType}:`); + logger.error(error); + throw error; } } public async runPostDeleteExternalCallbacks( - deletedPackagePolicies: PostDeletePackagePoliciesResponse + deletedPackagePolicies: PostDeletePackagePoliciesResponse, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise { const externalCallbacks = appContextService.getExternalCallbacks('packagePolicyPostDelete'); const errorsThrown: Error[] = []; @@ -1306,7 +1429,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { // Failures from an external callback should not prevent other external callbacks from being // executed. Errors (if any) will be collected and `throw`n after processing the entire set try { - await callback(deletedPackagePolicies); + await callback(deletedPackagePolicies, soClient, esClient, context, request); } catch (error) { errorsThrown.push(error); } @@ -1322,7 +1445,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } public async runDeleteExternalCallbacks( - deletedPackagePolices: DeletePackagePoliciesResponse + deletedPackagePolices: DeletePackagePoliciesResponse, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient ): Promise { const externalCallbacks = appContextService.getExternalCallbacks('packagePolicyDelete'); const errorsThrown: Error[] = []; @@ -1332,7 +1457,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient { // Failures from an external callback should not prevent other external callbacks from being // executed. Errors (if any) will be collected and `throw`n after processing the entire set try { - await callback(deletedPackagePolices); + await callback(deletedPackagePolices, soClient, esClient); } catch (error) { errorsThrown.push(error); } @@ -1398,7 +1523,9 @@ class PackagePolicyClientWithAuthz extends PackagePolicyClientImpl { skipUniqueNameVerification?: boolean; overwrite?: boolean; packageInfo?: PackageInfo; - } + }, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise { await this.#runPreflight({ fleetAuthz: { @@ -1406,7 +1533,7 @@ class PackagePolicyClientWithAuthz extends PackagePolicyClientImpl { }, }); - return super.create(soClient, esClient, packagePolicy, options); + return super.create(soClient, esClient, packagePolicy, options, context, request); } } diff --git a/x-pack/plugins/fleet/server/services/package_policy_service.ts b/x-pack/plugins/fleet/server/services/package_policy_service.ts index 790622a6ae6b4..5ca2a107c0281 100644 --- a/x-pack/plugins/fleet/server/services/package_policy_service.ts +++ b/x-pack/plugins/fleet/server/services/package_policy_service.ts @@ -5,12 +5,8 @@ * 2.0. */ -import type { KibanaRequest, Logger } from '@kbn/core/server'; -import type { - ElasticsearchClient, - RequestHandlerContext, - SavedObjectsClientContract, -} from '@kbn/core/server'; +import type { KibanaRequest, Logger, RequestHandlerContext } from '@kbn/core/server'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import type { AuthenticatedUser } from '@kbn/security-plugin/server'; import type { @@ -50,7 +46,9 @@ export interface PackagePolicyClient { skipUniqueNameVerification?: boolean; overwrite?: boolean; packageInfo?: PackageInfo; - } + }, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise; bulkCreate( @@ -108,7 +106,13 @@ export interface PackagePolicyClient { soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, ids: string[], - options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean; force?: boolean } + options?: { + user?: AuthenticatedUser; + skipUnassignFromAgentPolicies?: boolean; + force?: boolean; + }, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise; upgrade( @@ -146,9 +150,13 @@ export interface PackagePolicyClient { ? PostDeletePackagePoliciesResponse : A extends 'packagePolicyPostCreate' ? PackagePolicy + : A extends 'packagePolicyUpdate' + ? UpdatePackagePolicy : NewPackagePolicy, - context: RequestHandlerContext, - request: KibanaRequest + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise< A extends 'packagePolicyDelete' ? void @@ -156,13 +164,25 @@ export interface PackagePolicyClient { ? void : A extends 'packagePolicyPostCreate' ? PackagePolicy + : A extends 'packagePolicyUpdate' + ? UpdatePackagePolicy : NewPackagePolicy >; - runDeleteExternalCallbacks(deletedPackagePolicies: DeletePackagePoliciesResponse): Promise; + runDeleteExternalCallbacks( + deletedPackagePolicies: DeletePackagePoliciesResponse, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest + ): Promise; runPostDeleteExternalCallbacks( - deletedPackagePolicies: PostDeletePackagePoliciesResponse + deletedPackagePolicies: PostDeletePackagePoliciesResponse, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ): Promise; getUpgradePackagePolicyInfo( diff --git a/x-pack/plugins/fleet/server/services/saved_object.test.ts b/x-pack/plugins/fleet/server/services/saved_object.test.ts index d2683caf9c725..4dd99a3db2d2b 100644 --- a/x-pack/plugins/fleet/server/services/saved_object.test.ts +++ b/x-pack/plugins/fleet/server/services/saved_object.test.ts @@ -18,7 +18,7 @@ describe('Saved object service', () => { it('should escape quotes', () => { const res = escapeSearchQueryPhrase('test1"test2'); - expect(res).toEqual(`"test1\"test2"`); + expect(res).toEqual(`"test1\\"test2"`); }); }); }); diff --git a/x-pack/plugins/fleet/server/services/saved_object.ts b/x-pack/plugins/fleet/server/services/saved_object.ts index 6a45061d89334..2a1f5e216fb2f 100644 --- a/x-pack/plugins/fleet/server/services/saved_object.ts +++ b/x-pack/plugins/fleet/server/services/saved_object.ts @@ -16,7 +16,7 @@ import type { ListWithKuery } from '../types'; * @param val */ export function escapeSearchQueryPhrase(val: string): string { - return `"${val.replace(/["]/g, '"')}"`; + return `"${val.replace(/["]/g, '\\"')}"`; } // Adds `.attributes` to any kuery strings that are missing it, this comes from diff --git a/x-pack/plugins/fleet/server/services/security/security.test.ts b/x-pack/plugins/fleet/server/services/security/security.test.ts index f99a708504d6c..d40e2a40514c8 100644 --- a/x-pack/plugins/fleet/server/services/security/security.test.ts +++ b/x-pack/plugins/fleet/server/services/security/security.test.ts @@ -85,6 +85,9 @@ describe('When using calculateRouteAuthz()', () => { writeFileOperations: { executePackageAction: false, }, + writeExecuteOperations: { + executePackageAction: false, + }, }, }, diff --git a/x-pack/plugins/fleet/server/types/extensions.ts b/x-pack/plugins/fleet/server/types/extensions.ts index 6d3ebc32b523f..ca5a0d84c958e 100644 --- a/x-pack/plugins/fleet/server/types/extensions.ts +++ b/x-pack/plugins/fleet/server/types/extensions.ts @@ -6,6 +6,7 @@ */ import type { KibanaRequest, RequestHandlerContext } from '@kbn/core/server'; +import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server'; import type { DeepReadonly } from 'utility-types'; @@ -18,29 +19,43 @@ import type { } from '../../common/types'; export type PostPackagePolicyDeleteCallback = ( - packagePolicies: DeletePackagePoliciesResponse + packagePolicies: DeletePackagePoliciesResponse, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ) => Promise; export type PostPackagePolicyPostDeleteCallback = ( - deletedPackagePolicies: DeepReadonly + deletedPackagePolicies: DeepReadonly, + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ) => Promise; export type PostPackagePolicyCreateCallback = ( newPackagePolicy: NewPackagePolicy, - context: RequestHandlerContext, - request: KibanaRequest + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ) => Promise; export type PostPackagePolicyPostCreateCallback = ( packagePolicy: PackagePolicy, - context: RequestHandlerContext, - request: KibanaRequest + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ) => Promise; export type PutPackagePolicyUpdateCallback = ( updatePackagePolicy: UpdatePackagePolicy, - context: RequestHandlerContext, - request: KibanaRequest + soClient: SavedObjectsClientContract, + esClient: ElasticsearchClient, + context?: RequestHandlerContext, + request?: KibanaRequest ) => Promise; export type ExternalCallbackCreate = ['packagePolicyCreate', PostPackagePolicyCreateCallback]; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts index 96227b2c33bfe..92b0f098ae19d 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/agent.ts @@ -22,6 +22,7 @@ export const GetAgentsRequestSchema = { showInactive: schema.boolean({ defaultValue: false }), withMetrics: schema.boolean({ defaultValue: false }), showUpgradeable: schema.boolean({ defaultValue: false }), + getStatusSummary: schema.boolean({ defaultValue: false }), sortField: schema.maybe(schema.string()), sortOrder: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), }, diff --git a/x-pack/plugins/index_management/__jest__/a11y/indices_tab.a11y.test.ts b/x-pack/plugins/index_management/__jest__/a11y/indices_tab.a11y.test.ts index b926cdb6d034a..89717134002ec 100644 --- a/x-pack/plugins/index_management/__jest__/a11y/indices_tab.a11y.test.ts +++ b/x-pack/plugins/index_management/__jest__/a11y/indices_tab.a11y.test.ts @@ -19,8 +19,7 @@ import { createNonDataStreamIndex, } from '../client_integration/home/data_streams_tab.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/128836 -describe.skip('A11y Indices tab', () => { +describe('A11y Indices tab', () => { let testBed: IndicesTestBed; let httpSetup: ReturnType['httpSetup']; let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; @@ -54,7 +53,8 @@ describe.skip('A11y Indices tab', () => { await expectToBeAccessible(component); }); - describe('index details flyout', () => { + // FLAKY: https://github.com/elastic/kibana/issues/128836 + describe.skip('index details flyout', () => { beforeEach(async () => { httpRequestsMockHelpers.setLoadIndicesResponse([ createNonDataStreamIndex('non-data-stream-test-index'), diff --git a/x-pack/plugins/infra/common/alerting/metrics/types.ts b/x-pack/plugins/infra/common/alerting/metrics/types.ts index 60c01beacd158..4b72b6389b49f 100644 --- a/x-pack/plugins/infra/common/alerting/metrics/types.ts +++ b/x-pack/plugins/infra/common/alerting/metrics/types.ts @@ -33,6 +33,7 @@ export enum Aggregators { CARDINALITY = 'cardinality', P95 = 'p95', P99 = 'p99', + CUSTOM = 'custom', } export enum AlertStates { @@ -100,14 +101,43 @@ interface BaseMetricExpressionParams { export interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { aggType: Exclude; metric: string; + customMetrics: never; + equation: never; + label: never; } export interface CountMetricExpressionParams extends BaseMetricExpressionParams { aggType: Aggregators.COUNT; metric: never; + customMetrics: never; + equation: never; + label: never; } -export type MetricExpressionParams = NonCountMetricExpressionParams | CountMetricExpressionParams; +export type CustomMetricAggTypes = Exclude< + Aggregators, + Aggregators.CUSTOM | Aggregators.RATE | Aggregators.P95 | Aggregators.P99 +>; + +export interface MetricExpressionCustomMetric { + name: string; + aggType: CustomMetricAggTypes; + field?: string; + filter?: string; +} + +export interface CustomMetricExpressionParams extends BaseMetricExpressionParams { + aggType: Aggregators.CUSTOM; + metric: never; + customMetrics: MetricExpressionCustomMetric[]; + equation?: string; + label?: string; +} + +export type MetricExpressionParams = + | NonCountMetricExpressionParams + | CountMetricExpressionParams + | CustomMetricExpressionParams; export const QUERY_INVALID: unique symbol = Symbol('QUERY_INVALID'); diff --git a/x-pack/plugins/infra/common/formatters/telemetry_time_range.ts b/x-pack/plugins/infra/common/formatters/telemetry_time_range.ts new file mode 100644 index 0000000000000..1befe71289ae4 --- /dev/null +++ b/x-pack/plugins/infra/common/formatters/telemetry_time_range.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. + */ + +const ONE_MINUTE = 60000; +const FIVE_MINUTES = ONE_MINUTE * 5; +const TEN_MINUTES = ONE_MINUTE * 10; +const THIRTY_MINUTES = ONE_MINUTE * 30; +const ONE_HOUR = ONE_MINUTE * 60; +const TWO_HOURS = ONE_HOUR * 2; +const EIGHT_HOURS = ONE_HOUR * 8; +const TWELVE_HOURS = ONE_HOUR * 12; +const ONE_DAY = ONE_HOUR * 24; +const TWO_DAYS = ONE_DAY * 2; +const SEVEN_DAYS = ONE_DAY * 7; +const FOURTEEN_DAYS = ONE_DAY * 14; +const THIRTY_DAYS = ONE_DAY * 30; +const SIXTY_DAYS = ONE_DAY * 60; +const NINETY_DAYS = ONE_DAY * 90; +const HALF_YEAR = ONE_DAY * 180; +const ONE_YEAR = ONE_DAY * 365; + +export const telemetryTimeRangeFormatter = (ms: number): string => { + if (ms < ONE_MINUTE) return '1. Less than 1 minute'; + if (ms >= ONE_MINUTE && ms < FIVE_MINUTES) return '2. 1-5 minutes'; + if (ms >= FIVE_MINUTES && ms < TEN_MINUTES) return '3. 5-10 minutes'; + if (ms >= TEN_MINUTES && ms < THIRTY_MINUTES) return '4. 10-30 minutes'; + if (ms >= THIRTY_MINUTES && ms < ONE_HOUR) return '5. 30-60 minutes'; + if (ms >= ONE_HOUR && ms < TWO_HOURS) return '6. 1-2 hours'; + if (ms >= TWO_HOURS && ms < EIGHT_HOURS) return '7. 2-8 hours'; + if (ms >= EIGHT_HOURS && ms < TWELVE_HOURS) return '8. 8-12 hours'; + if (ms >= TWELVE_HOURS && ms < ONE_DAY) return '9. 12-24 hours'; + if (ms >= ONE_DAY && ms < TWO_DAYS) return '10. 1-2 days'; + if (ms >= TWO_DAYS && ms < SEVEN_DAYS) return '11. 2-7 days'; + if (ms >= SEVEN_DAYS && ms < FOURTEEN_DAYS) return '12. 7-14 days'; + if (ms >= FOURTEEN_DAYS && ms < THIRTY_DAYS) return '13. 14-30 days'; + if (ms >= THIRTY_DAYS && ms < SIXTY_DAYS) return '14. 30-60 days'; + if (ms >= SIXTY_DAYS && ms < NINETY_DAYS) return '15. 60-90 days'; + if (ms >= NINETY_DAYS && ms < HALF_YEAR) return '16. 90-180 days'; + if (ms >= HALF_YEAR && ms < ONE_YEAR) return '17. 180-365 days'; + return '18. More than 1 year'; +}; diff --git a/x-pack/plugins/infra/common/http_api/metrics_explorer.ts b/x-pack/plugins/infra/common/http_api/metrics_explorer.ts index de00d521126e3..d735e398e6661 100644 --- a/x-pack/plugins/infra/common/http_api/metrics_explorer.ts +++ b/x-pack/plugins/infra/common/http_api/metrics_explorer.ts @@ -6,6 +6,7 @@ */ import * as rt from 'io-ts'; +import { xor } from 'lodash'; export const METRIC_EXPLORER_AGGREGATIONS = [ 'avg', @@ -17,8 +18,11 @@ export const METRIC_EXPLORER_AGGREGATIONS = [ 'sum', 'p95', 'p99', + 'custom', ] as const; +export const OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS = ['custom', 'rate', 'p95', 'p99']; + type MetricExplorerAggregations = typeof METRIC_EXPLORER_AGGREGATIONS[number]; const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< @@ -27,12 +31,42 @@ const metricsExplorerAggregationKeys = METRIC_EXPLORER_AGGREGATIONS.reduce< export const metricsExplorerAggregationRT = rt.keyof(metricsExplorerAggregationKeys); +export type MetricExplorerCustomMetricAggregations = Exclude< + MetricsExplorerAggregation, + 'custom' | 'rate' | 'p95' | 'p99' +>; +const metricsExplorerCustomMetricAggregationKeys = xor( + METRIC_EXPLORER_AGGREGATIONS, + OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS +).reduce>( + (acc, agg) => ({ ...acc, [agg]: null }), + {} as Record +); +export const metricsExplorerCustomMetricAggregationRT = rt.keyof( + metricsExplorerCustomMetricAggregationKeys +); + export const metricsExplorerMetricRequiredFieldsRT = rt.type({ aggregation: metricsExplorerAggregationRT, }); +export const metricsExplorerCustomMetricRT = rt.intersection([ + rt.type({ + name: rt.string, + aggregation: metricsExplorerCustomMetricAggregationRT, + }), + rt.partial({ + field: rt.string, + filter: rt.string, + }), +]); + +export type MetricsExplorerCustomMetric = rt.TypeOf; + export const metricsExplorerMetricOptionalFieldsRT = rt.partial({ field: rt.union([rt.string, rt.undefined]), + custom_metrics: rt.array(metricsExplorerCustomMetricRT), + equation: rt.string, }); export const metricsExplorerMetricRT = rt.intersection([ diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx new file mode 100644 index 0000000000000..ce30172a74f15 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.stories.tsx @@ -0,0 +1,106 @@ +/* + * 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 { Meta, Story } from '@storybook/react/types-6-0'; +import React, { useCallback, useEffect, useState } from 'react'; +import { TimeUnitChar } from '@kbn/observability-plugin/common'; +import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { + Aggregators, + Comparator, + MetricExpressionParams, +} from '../../../../../common/alerting/metrics'; +import { decorateWithGlobalStorybookThemeProviders } from '../../../../test_utils/use_global_storybook_theme'; +import { CustomEquationEditor, CustomEquationEditorProps } from './custom_equation_editor'; +import { aggregationType } from '../expression_row'; +import { MetricExpression } from '../../types'; +import { validateMetricThreshold } from '../validation'; + +export default { + title: 'infra/alerting/CustomEquationEditor', + decorators: [ + (wrappedStory) =>
{wrappedStory()}
, + decorateWithGlobalStorybookThemeProviders, + ], + parameters: { + layout: 'padded', + }, + argTypes: { + onChange: { action: 'changed' }, + }, +} as Meta; + +const CustomEquationEditorTemplate: Story = (args) => { + const [expression, setExpression] = useState(args.expression); + const [errors, setErrors] = useState(args.errors); + + const handleExpressionChange = useCallback( + (exp: MetricExpression) => { + setExpression(exp); + args.onChange(exp); + return exp; + }, + [args] + ); + + useEffect(() => { + const validationObject = validateMetricThreshold({ + criteria: [expression as MetricExpressionParams], + }); + setErrors(validationObject.errors[0]); + }, [expression]); + + return ( + + ); +}; + +export const CustomEquationEditorDefault = CustomEquationEditorTemplate.bind({}); +export const CustomEquationEditorWithEquationErrors = CustomEquationEditorTemplate.bind({}); +export const CustomEquationEditorWithFieldError = CustomEquationEditorTemplate.bind({}); + +const BASE_ARGS = { + expression: { + aggType: Aggregators.CUSTOM, + timeSize: 1, + timeUnit: 'm' as TimeUnitChar, + threshold: [1], + comparator: Comparator.GT, + }, + fields: [ + { name: 'system.cpu.user.pct', normalizedType: 'number' }, + { name: 'system.cpu.system.pct', normalizedType: 'number' }, + { name: 'system.cpu.cores', normalizedType: 'number' }, + ], + aggregationTypes: aggregationType, +}; + +CustomEquationEditorDefault.args = { + ...BASE_ARGS, + errors: {}, +}; + +CustomEquationEditorWithEquationErrors.args = { + ...BASE_ARGS, + expression: { + ...BASE_ARGS.expression, + equation: 'Math.round(A / B)', + customMetrics: [ + { name: 'A', aggType: Aggregators.AVERAGE, field: 'system.cpu.user.pct' }, + { name: 'B', aggType: Aggregators.MAX, field: 'system.cpu.cores' }, + ], + }, + errors: { + equation: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + }, +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx new file mode 100644 index 0000000000000..866e818688533 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/custom_equation_editor.tsx @@ -0,0 +1,214 @@ +/* + * 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 { + EuiFieldText, + EuiFormRow, + EuiFlexItem, + EuiFlexGroup, + EuiButtonEmpty, + EuiSpacer, +} from '@elastic/eui'; +import React, { useState, useCallback, useMemo } from 'react'; +import { omit, range, first, xor, debounce } from 'lodash'; +import { IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS } from '../../../../../common/http_api'; +import { + Aggregators, + CustomMetricAggTypes, + MetricExpressionCustomMetric, +} from '../../../../../common/alerting/metrics'; +import { MetricExpression } from '../../types'; +import { CustomMetrics, AggregationTypes, NormalizedFields } from './types'; +import { MetricRowWithAgg } from './metric_row_with_agg'; +import { MetricRowWithCount } from './metric_row_with_count'; +import { + CUSTOM_EQUATION, + EQUATION_HELP_MESSAGE, + LABEL_HELP_MESSAGE, + LABEL_LABEL, +} from '../../i18n_strings'; + +export interface CustomEquationEditorProps { + onChange: (expression: MetricExpression) => void; + expression: MetricExpression; + fields: NormalizedFields; + aggregationTypes: AggregationTypes; + errors: IErrorObject; +} + +const NEW_METRIC = { name: 'A', aggType: Aggregators.AVERAGE as CustomMetricAggTypes }; +const MAX_VARIABLES = 26; +const CHAR_CODE_FOR_A = 65; +const CHAR_CODE_FOR_Z = CHAR_CODE_FOR_A + MAX_VARIABLES; +const VAR_NAMES = range(CHAR_CODE_FOR_A, CHAR_CODE_FOR_Z).map((c) => String.fromCharCode(c)); + +export const CustomEquationEditor = ({ + onChange, + expression, + fields, + aggregationTypes, + errors, +}: CustomEquationEditorProps) => { + const [customMetrics, setCustomMetrics] = useState( + expression?.customMetrics ?? [NEW_METRIC] + ); + const [label, setLabel] = useState(expression?.label || undefined); + const [equation, setEquation] = useState(expression?.equation || undefined); + const debouncedOnChange = useMemo(() => debounce(onChange, 500), [onChange]); + + const handleAddNewRow = useCallback(() => { + setCustomMetrics((previous) => { + const currentVars = previous?.map((m) => m.name) ?? []; + const name = first(xor(VAR_NAMES, currentVars))!; + const nextMetrics = [...(previous || []), { ...NEW_METRIC, name }]; + debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation, label }); + return nextMetrics; + }); + }, [debouncedOnChange, equation, expression, label]); + + const handleDelete = useCallback( + (name: string) => { + setCustomMetrics((previous) => { + const nextMetrics = previous?.filter((row) => row.name !== name) ?? [NEW_METRIC]; + const finalMetrics = (nextMetrics.length && nextMetrics) || [NEW_METRIC]; + debouncedOnChange({ ...expression, customMetrics: finalMetrics, equation, label }); + return finalMetrics; + }); + }, + [equation, expression, debouncedOnChange, label] + ); + + const handleChange = useCallback( + (metric: MetricExpressionCustomMetric) => { + setCustomMetrics((previous) => { + const nextMetrics = previous?.map((m) => (m.name === metric.name ? metric : m)); + debouncedOnChange({ ...expression, customMetrics: nextMetrics, equation, label }); + return nextMetrics; + }); + }, + [equation, expression, debouncedOnChange, label] + ); + + const handleEquationChange = useCallback( + (e: React.ChangeEvent) => { + setEquation(e.target.value); + debouncedOnChange({ ...expression, customMetrics, equation: e.target.value, label }); + }, + [debouncedOnChange, expression, customMetrics, label] + ); + + const handleLabelChange = useCallback( + (e: React.ChangeEvent) => { + setLabel(e.target.value); + debouncedOnChange({ ...expression, customMetrics, equation, label: e.target.value }); + }, + [debouncedOnChange, expression, customMetrics, equation] + ); + + const disableAdd = customMetrics?.length === MAX_VARIABLES; + const disableDelete = customMetrics?.length === 1; + + const filteredAggregationTypes = omit(aggregationTypes, OMITTED_AGGREGATIONS_FOR_CUSTOM_METRICS); + + const metricRows = customMetrics?.map((row) => { + if (row.aggType === Aggregators.COUNT) { + return ( + + ); + } + return ( + + ); + }); + + const placeholder = useMemo(() => { + return customMetrics?.map((row) => row.name).join(' + '); + }, [customMetrics]); + + return ( +
+ + {metricRows} + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/index.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/index.tsx new file mode 100644 index 0000000000000..2c885581b3989 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/index.tsx @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { CustomEquationEditor } from './custom_equation_editor'; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_controls.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_controls.tsx new file mode 100644 index 0000000000000..3c8efe19a01d7 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_controls.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 from 'react'; +import { EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; +import { DELETE_LABEL } from '../../i18n_strings'; + +interface MetricRowControlProps { + onDelete: () => void; + disableDelete: boolean; +} + +export const MetricRowControls = ({ onDelete, disableDelete }: MetricRowControlProps) => { + return ( + <> + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_agg.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_agg.tsx new file mode 100644 index 0000000000000..1e80f743d7967 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_agg.tsx @@ -0,0 +1,137 @@ +/* + * 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 { + EuiFormRow, + EuiHorizontalRule, + EuiFlexItem, + EuiFlexGroup, + EuiSelect, + EuiComboBox, + EuiComboBoxOptionOption, +} from '@elastic/eui'; +import React, { useMemo, useCallback } from 'react'; +import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { Aggregators, CustomMetricAggTypes } from '../../../../../common/alerting/metrics'; +import { MetricRowControls } from './metric_row_controls'; +import { NormalizedFields, MetricRowBaseProps } from './types'; + +interface MetricRowWithAggProps extends MetricRowBaseProps { + aggType?: CustomMetricAggTypes; + field?: string; + fields: NormalizedFields; +} + +export const MetricRowWithAgg = ({ + name, + aggType = Aggregators.AVERAGE, + field, + onDelete, + disableDelete, + fields, + aggregationTypes, + onChange, + errors, +}: MetricRowWithAggProps) => { + const handleDelete = useCallback(() => { + onDelete(name); + }, [name, onDelete]); + + const fieldOptions = useMemo( + () => + fields.reduce((acc, fieldValue) => { + if ( + aggType && + aggregationTypes[aggType].validNormalizedTypes.includes(fieldValue.normalizedType) + ) { + acc.push({ label: fieldValue.name }); + } + return acc; + }, [] as Array<{ label: string }>), + [fields, aggregationTypes, aggType] + ); + + const aggOptions = useMemo( + () => + Object.values(aggregationTypes).map((a) => ({ + text: a.text, + value: a.value, + })), + [aggregationTypes] + ); + + const handleFieldChange = useCallback( + (selectedOptions: EuiComboBoxOptionOption[]) => { + onChange({ + name, + field: (selectedOptions.length && selectedOptions[0].label) || undefined, + aggType, + }); + }, + [name, aggType, onChange] + ); + + const handleAggChange = useCallback( + (el: React.ChangeEvent) => { + onChange({ + name, + field, + aggType: el.target.value as CustomMetricAggTypes, + }); + }, + [name, field, onChange] + ); + + const isAggInvalid = get(errors, ['customMetrics', name, 'aggType']) != null; + const isFieldInvalid = get(errors, ['customMetrics', name, 'field']) != null || !field; + + return ( + <> + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx new file mode 100644 index 0000000000000..43ac682830bcd --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/metric_row_with_count.tsx @@ -0,0 +1,100 @@ +/* + * 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 { + EuiFieldText, + EuiFormRow, + EuiHorizontalRule, + EuiFlexItem, + EuiFlexGroup, + EuiSelect, +} from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { Aggregators, CustomMetricAggTypes } from '../../../../../common/alerting/metrics'; +import { MetricRowControls } from './metric_row_controls'; +import { MetricRowBaseProps } from './types'; + +interface MetricRowWithCountProps extends MetricRowBaseProps { + agg?: Aggregators; + filter?: string; +} + +export const MetricRowWithCount = ({ + name, + agg, + filter, + onDelete, + disableDelete, + onChange, + aggregationTypes, +}: MetricRowWithCountProps) => { + const aggOptions = useMemo( + () => + Object.values(aggregationTypes) + .filter((aggType) => aggType.value !== Aggregators.CUSTOM) + .map((aggType) => ({ + text: aggType.text, + value: aggType.value, + })), + [aggregationTypes] + ); + + const handleDelete = useCallback(() => { + onDelete(name); + }, [name, onDelete]); + + const handleAggChange = useCallback( + (el: React.ChangeEvent) => { + onChange({ + name, + filter, + aggType: el.target.value as CustomMetricAggTypes, + }); + }, + [name, filter, onChange] + ); + + const handleFilterChange = useCallback( + (el: React.ChangeEvent) => { + onChange({ + name, + filter: el.target.value, + aggType: agg as CustomMetricAggTypes, + }); + }, + [name, agg, onChange] + ); + + return ( + <> + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/types.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/types.ts new file mode 100644 index 0000000000000..60069c6bb79d2 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/custom_equation/types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { AggregationType, IErrorObject } from '@kbn/triggers-actions-ui-plugin/public'; +import { MetricExpressionCustomMetric } from '../../../../../common/alerting/metrics'; +import { MetricExpression } from '../../types'; + +export type CustomMetrics = MetricExpression['customMetrics']; + +export interface AggregationTypes { + [x: string]: AggregationType; +} + +export interface NormalizedField { + name: string; + normalizedType: string; +} + +export type NormalizedFields = NormalizedField[]; + +export interface MetricRowBaseProps { + name: string; + onAdd: () => void; + onDelete: (name: string) => void; + disableDelete: boolean; + disableAdd: boolean; + onChange: (metric: MetricExpressionCustomMetric) => void; + aggregationTypes: AggregationTypes; + errors: IErrorObject; +} diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx index 87bc52322c7d3..8fe00f5a34c73 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.tsx @@ -17,7 +17,10 @@ import { Color } from '../../../../common/color_palette'; import { MetricsExplorerRow, MetricsExplorerAggregation } from '../../../../common/http_api'; import { MetricExplorerSeriesChart } from '../../../pages/metrics/metrics_explorer/components/series_chart'; import { MetricExpression } from '../types'; -import { MetricsExplorerChartType } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; +import { + MetricsExplorerChartType, + MetricsExplorerOptionsMetric, +} from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; import { createFormatterForMetric } from '../../../pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric'; import { calculateDomain } from '../../../pages/metrics/metrics_explorer/components/helpers/calculate_domain'; import { useMetricsExplorerChartData } from '../hooks/use_metrics_explorer_chart_data'; @@ -32,6 +35,7 @@ import { getChartTheme, } from '../../common/criterion_preview_chart/criterion_preview_chart'; import { ThresholdAnnotations } from '../../common/criterion_preview_chart/threshold_annotations'; +import { CUSTOM_EQUATION } from '../i18n_strings'; interface Props { expression: MetricExpression; @@ -58,11 +62,15 @@ export const ExpressionChart: React.FC = ({ const { uiSettings } = useKibanaContextForPlugin().services; - const metric = { + const metric: MetricsExplorerOptionsMetric = { field: expression.metric, aggregation: expression.aggType as MetricsExplorerAggregation, color: Color.color0, }; + + if (metric.aggregation === 'custom') { + metric.label = expression.label || CUSTOM_EQUATION; + } const isDarkMode = uiSettings?.get('theme:darkMode') || false; const dateFormatter = useMemo(() => { const firstSeries = first(data?.series); @@ -79,10 +87,14 @@ export const ExpressionChart: React.FC = ({ /* eslint-disable-next-line react-hooks/exhaustive-deps */ const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]); - if (loading || !data) { + if (loading) { return ; } + if (!data) { + return ; + } + const criticalThresholds = expression.threshold.slice().sort(); const warningThresholds = expression.warningThreshold?.slice().sort() ?? []; const thresholds = [...criticalThresholds, ...warningThresholds].sort(); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx index 2204dd16fd46a..14ff5b1e60eed 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx @@ -20,16 +20,19 @@ import { omit } from 'lodash'; import React, { useCallback, useMemo, useState } from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; import { + AggregationType, builtInComparators, IErrorObject, OfExpression, ThresholdExpression, WhenExpression, } from '@kbn/triggers-actions-ui-plugin/public'; -import { Comparator } from '../../../../common/alerting/metrics'; +import { Aggregators, Comparator } from '../../../../common/alerting/metrics'; import { decimalToPct, pctToDecimal } from '../../../../common/utils/corrected_percent_convert'; import { DerivedIndexPattern } from '../../../containers/metrics_source'; import { AGGREGATION_TYPES, MetricExpression } from '../types'; +import { CustomEquationEditor } from './custom_equation'; +import { CUSTOM_EQUATION } from '../i18n_strings'; const customComparators = { ...builtInComparators, @@ -73,6 +76,7 @@ export const ExpressionRow: React.FC = (props) => { const toggleRowState = useCallback(() => setRowState(!isExpanded), [isExpanded]); const { children, setRuleParams, expression, errors, expressionId, remove, fields, canDelete } = props; + const { aggType = AGGREGATION_TYPES.MAX, metric, @@ -92,7 +96,10 @@ export const ExpressionRow: React.FC = (props) => { setRuleParams(expressionId, { ...expression, aggType: at as MetricExpression['aggType'], - metric: at === 'count' ? undefined : expression.metric, + metric: ['custom', 'count'].includes(at) ? undefined : expression.metric, + customMetrics: at === 'custom' ? expression.customMetrics : undefined, + equation: at === 'custom' ? expression.equation : undefined, + label: at === 'custom' ? expression.label : undefined, }); }, [expressionId, expression, setRuleParams] @@ -166,6 +173,13 @@ export const ExpressionRow: React.FC = (props) => { expressionId, ]); + const handleCustomMetricChange = useCallback( + (exp) => { + setRuleParams(expressionId, exp); + }, + [expressionId, setRuleParams] + ); + const criticalThresholdExpression = ( = (props) => { /> ); + const normalizedFields = fields.map((f) => ({ + normalizedType: f.type, + name: f.name, + })); + return ( <> @@ -201,7 +220,7 @@ export const ExpressionRow: React.FC = (props) => { />
- + = (props) => { onChangeSelectedAggType={updateAggType} /> - {aggType !== 'count' && ( + {!['count', 'custom'].includes(aggType) && ( ({ - normalizedType: f.type, - name: f.name, - }))} + fields={normalizedFields} aggType={aggType} errors={errors} onChangeSelectedAggField={updateMetric} @@ -245,6 +261,25 @@ export const ExpressionRow: React.FC = (props) => { )} {!displayWarningThreshold && criticalThresholdExpression} + {!displayWarningThreshold && ( + <> + + + + + + + + )} {displayWarningThreshold && ( <> @@ -280,24 +315,19 @@ export const ExpressionRow: React.FC = (props) => { )} - {!displayWarningThreshold && ( + {aggType === Aggregators.CUSTOM && ( <> - {' '} - + - - - + + )} @@ -358,7 +388,7 @@ const ThresholdElement: React.FC<{ ); }; -export const aggregationType: { [key: string]: any } = { +export const aggregationType: { [key: string]: AggregationType } = { avg: { text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', { defaultMessage: 'Average', @@ -431,4 +461,10 @@ export const aggregationType: { [key: string]: any } = { value: AGGREGATION_TYPES.P99, validNormalizedTypes: ['number', 'histogram'], }, + custom: { + text: CUSTOM_EQUATION, + fieldRequired: false, + value: AGGREGATION_TYPES.CUSTOM, + validNormalizedTypes: ['number', 'histogram'], + }, }; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.test.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.test.ts new file mode 100644 index 0000000000000..ac1b545e4a302 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.test.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EQUATION_REGEX } from './validation'; + +describe('Metric Threshold Validation', () => { + describe('valid equations', () => { + const validExpression = [ + '(A + B) / 100', + '(A - B) * 100', + 'A > 1 ? A : B', + 'A <= 1 ? A : B', + 'A && B || C', + ]; + validExpression.forEach((exp) => { + it(exp, () => { + expect(exp.match(EQUATION_REGEX)).toBeFalsy(); + }); + }); + }); + describe('invalid equations', () => { + const validExpression = ['Math.round(A + B) / 100', '(A^2 - B) * 100']; + validExpression.forEach((exp) => { + it(exp, () => { + expect(exp.match(EQUATION_REGEX)).toBeTruthy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx index bc75b2512fbc1..b3d4d423c58b5 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/validation.tsx @@ -7,13 +7,24 @@ import { i18n } from '@kbn/i18n'; import { ValidationResult } from '@kbn/triggers-actions-ui-plugin/public'; +import { isEmpty } from 'lodash'; import { + Aggregators, Comparator, + CustomMetricExpressionParams, FilterQuery, MetricExpressionParams, QUERY_INVALID, } from '../../../../common/alerting/metrics'; +export const EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; + +const isCustomMetricExpressionParams = ( + subject: MetricExpressionParams +): subject is CustomMetricExpressionParams => { + return subject.aggType === Aggregators.CUSTOM; +}; + export function validateMetricThreshold({ criteria, filterQuery, @@ -36,6 +47,9 @@ export function validateMetricThreshold({ threshold1: string[]; }; metric: string[]; + customMetricsError?: string; + customMetrics: Record; + equation?: string; }; } & { filterQuery?: string[] } = {}; validationResult.errors = errors; @@ -70,6 +84,7 @@ export function validateMetricThreshold({ }, metric: [], filterQuery: [], + customMetrics: {}, }; if (!c.aggType) { errors[id].aggField.push( @@ -136,16 +151,59 @@ export function validateMetricThreshold({ ); } - if (!c.metric && c.aggType !== 'count') { + if (!c.metric && c.aggType !== 'count' && c.aggType !== 'custom') { errors[id].metric.push( i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', { defaultMessage: 'Metric is required.', }) ); } + + if (isCustomMetricExpressionParams(c)) { + if (!c.customMetrics || (c.customMetrics && c.customMetrics.length < 1)) { + errors[id].customMetricsError = i18n.translate( + 'xpack.infra.metrics.alertFlyout.error.customMetricsError', + { + defaultMessage: 'You must define at least 1 custom metric', + } + ); + } else { + c.customMetrics.forEach((metric) => { + const customMetricErrors: { aggType?: string; field?: string } = {}; + if (!metric.aggType) { + customMetricErrors.aggType = i18n.translate( + 'xpack.infra.metrics.alertFlyout.error.customMetrics.aggTypeRequired', + { + defaultMessage: 'Aggregation is required', + } + ); + } + if (metric.aggType !== 'count' && !metric.field) { + customMetricErrors.field = i18n.translate( + 'xpack.infra.metrics.alertFlyout.error.customMetrics.fieldRequired', + { + defaultMessage: 'Field is required', + } + ); + } + if (!isEmpty(customMetricErrors)) { + errors[id].customMetrics[metric.name] = customMetricErrors; + } + }); + } + + if (c.equation && c.equation.match(EQUATION_REGEX)) { + errors[id].equation = i18n.translate( + 'xpack.infra.metrics.alertFlyout.error.equation.invalidCharacters', + { + defaultMessage: + 'The equation field only supports the following characters: A-Z, +, -, /, *, (, ), ?, !, &, :, |, >, <, =', + } + ); + } + } }); return validationResult; } - const isNumber = (value: unknown): value is number => typeof value === 'number'; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts index 1ae1bacfed42e..6fcbd6df75918 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/hooks/use_metrics_explorer_chart_data.ts @@ -7,10 +7,12 @@ import { DataViewBase } from '@kbn/es-query'; import { useMemo } from 'react'; +import { MetricExpressionCustomMetric } from '../../../../common/alerting/metrics'; import { MetricsSourceConfiguration } from '../../../../common/metrics_sources'; import { MetricExpression } from '../types'; import { MetricsExplorerOptions } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options'; import { useMetricsExplorerData } from '../../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data'; +import { MetricExplorerCustomMetricAggregations } from '../../../../common/http_api/metrics_explorer'; export const useMetricsExplorerChartData = ( expression: MetricExpression, @@ -20,6 +22,7 @@ export const useMetricsExplorerChartData = ( groupBy?: string | string[] ) => { const { timeSize, timeUnit } = expression || { timeSize: 1, timeUnit: 'm' }; + const options: MetricsExplorerOptions = useMemo( () => ({ limit: 1, @@ -28,14 +31,26 @@ export const useMetricsExplorerChartData = ( groupBy, filterQuery, metrics: [ - { - field: expression.metric, - aggregation: expression.aggType, - }, + expression.aggType === 'custom' + ? { + aggregation: 'custom', + custom_metrics: + expression?.customMetrics?.map(mapMetricThresholdMetricToMetricsExplorerMetric) ?? + [], + equation: expression.equation, + } + : { field: expression.metric, aggregation: expression.aggType }, ], aggregation: expression.aggType || 'avg', }), - [expression.aggType, expression.metric, filterQuery, groupBy] + [ + expression.aggType, + expression.equation, + expression.metric, + expression.customMetrics, + filterQuery, + groupBy, + ] ); const timerange = useMemo( () => ({ @@ -55,3 +70,19 @@ export const useMetricsExplorerChartData = ( null ); }; + +const mapMetricThresholdMetricToMetricsExplorerMetric = (metric: MetricExpressionCustomMetric) => { + if (metric.aggType === 'count') { + return { + name: metric.name, + aggregation: 'count' as MetricExplorerCustomMetricAggregations, + filter: metric.filter, + }; + } + + return { + name: metric.name, + aggregation: metric.aggType as MetricExplorerCustomMetricAggregations, + field: metric.field, + }; +}; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/i18n_strings.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/i18n_strings.ts new file mode 100644 index 0000000000000..a2778bfc6b832 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/i18n_strings.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const EQUATION_HELP_MESSAGE = i18n.translate( + 'xpack.infra.metrics.alertFlyout.customEquationEditor.equationHelpMessage', + { defaultMessage: 'Supports basic math expressions' } +); + +export const LABEL_LABEL = i18n.translate( + 'xpack.infra.metrics.alertFlyout.customEquationEditor.labelLabel', + { defaultMessage: 'Label (optional)' } +); + +export const LABEL_HELP_MESSAGE = i18n.translate( + 'xpack.infra.metrics.alertFlyout.customEquationEditor.labelHelpMessage', + { + defaultMessage: 'Custom label will show on the alert chart and in reason/alert title', + } +); + +export const CUSTOM_EQUATION = i18n.translate('xpack.infra.metrics.alertFlyout.customEquation', { + defaultMessage: 'Custom equation', +}); + +export const DELETE_LABEL = i18n.translate( + 'xpack.infra.metrics.alertFlyout.customEquationEditor.deleteRowButton', + { defaultMessage: 'Delete' } +); + +export const AGGREGATION_LABEL = (name: string) => + i18n.translate('xpack.infra.metrics.alertFlyout.customEquationEditor.aggregationLabel', { + defaultMessage: 'Aggregation {name}', + values: { name }, + }); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts index a88dd1d4548b8..aa9336cb6023d 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/types.ts @@ -14,8 +14,14 @@ export interface AlertContextMeta { series?: MetricsExplorerSeries; } -export type MetricExpression = Omit & { +export type MetricExpression = Omit< + MetricExpressionParams, + 'metric' | 'timeSize' | 'timeUnit' | 'metrics' | 'equation' | 'customMetrics' +> & { metric?: MetricExpressionParams['metric']; + customMetrics?: MetricExpressionParams['customMetrics']; + label?: MetricExpressionParams['label']; + equation?: MetricExpressionParams['equation']; timeSize?: MetricExpressionParams['timeSize']; timeUnit?: MetricExpressionParams['timeUnit']; }; @@ -30,6 +36,7 @@ export enum AGGREGATION_TYPES { CARDINALITY = 'cardinality', P95 = 'p95', P99 = 'p99', + CUSTOM = 'custom', } export interface MetricThresholdAlertParams { diff --git a/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts b/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts index a42e43a71771e..32f7de00ff645 100644 --- a/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts +++ b/x-pack/plugins/infra/public/common/visualizations/lens/utils.ts @@ -70,7 +70,7 @@ export const getXYVisualizationState = ( showSingleSeries: false, }, valueLabels: 'show', - fittingFunction: 'None', + fittingFunction: 'Zero', curveType: 'LINEAR', yLeftScale: 'linear', axisTitlesVisibilitySettings: { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_dataset_filtering.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_dataset_filtering.ts index 5bcf6455310b9..6f19fb1c0549c 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_dataset_filtering.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_dataset_filtering.ts @@ -32,9 +32,21 @@ function reducer(state: ReducerState, action: ReducerAction) { selectedDatasets: action.payload.datasets, }; case 'updateDatasetsFilters': - const datasetsToAdd = action.payload.filters - .filter((filter) => !state.selectedDatasets.includes(filter.meta.params.query)) - .map((filter) => filter.meta.params.query); + const datasetsToAdd = action.payload.filters.reduce( + (prevFilters: string[], nextFilter: Filter) => { + const query = + typeof nextFilter.meta.params === 'object' && + 'query' in nextFilter.meta.params && + nextFilter.meta.params?.query; + const queryString = query ? String(query) : ''; + if (!state.selectedDatasets.includes(queryString)) { + prevFilters.push(queryString); + } + return prevFilters; + }, + [] + ); + return { ...state, selectedDatasets: [...state.selectedDatasets, ...datasetsToAdd], @@ -77,11 +89,16 @@ export const useDatasetFiltering = () => { // be re-added via the embeddable as it will be seen as a duplicate to the FilterManager, // and no update will be emitted. useEffect(() => { - const filtersToRemove = reducerState.selectedDatasetsFilters.filter( - (filter) => !reducerState.selectedDatasets.includes(filter.meta.params.query) - ); + const filtersToRemove = reducerState.selectedDatasetsFilters.filter((filter: Filter) => { + const query = + typeof filter.meta.params === 'object' && + 'query' in filter.meta.params && + filter.meta.params?.query; + const queryString = query ? String(query) : ''; + return !reducerState.selectedDatasets.includes(queryString); + }); if (filtersToRemove.length > 0) { - filtersToRemove.forEach((filter) => { + filtersToRemove.forEach((filter: Filter) => { services.data.query.filterManager.removeFilter(filter); }); } diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/cloud_provider_icon_with_title.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/cloud_provider_icon_with_title.tsx deleted file mode 100644 index be28f1ad3b445..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/cloud_provider_icon_with_title.tsx +++ /dev/null @@ -1,53 +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 { EuiToolTip } from '@elastic/eui'; -import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; - -const cloudIcons: Record = { - gcp: 'logoGCP', - aws: 'logoAWS', - azure: 'logoAzure', - unknownProvider: 'cloudSunny', -}; - -export const CloudProviderIconWithTitle = ({ - provider, - title, - text, -}: { - provider?: string | null; - title?: React.ReactNode; - text: string; -}) => { - return ( - - - - - - - - {title ?? ( - - {text} - - )} - - - ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 65e4b8f3d1c25..de2d046c08d19 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -5,11 +5,10 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { EuiInMemoryTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isEqual } from 'lodash'; -import { buildHostsTableColumns } from './hosts_table_columns'; import { NoData } from '../../../../components/empty_states'; import { InfraLoadingPanel } from '../../../../components/loading'; import { useHostsTable } from '../hooks/use_hosts_table'; @@ -41,6 +40,8 @@ export const HostsTable = () => { metrics: HOST_TABLE_METRICS, }); + const { columns, items } = useHostsTable(nodes, { time: unifiedSearchDateRange }); + useEffect(() => { if (hostViewState.loading !== loading || nodes.length !== hostViewState.totalHits) { setHostViewState({ @@ -58,7 +59,6 @@ export const HostsTable = () => { setHostViewState, ]); - const items = useHostsTable(nodes); const noData = items.length === 0; const onTableChange = useCallback( @@ -79,11 +79,6 @@ export const HostsTable = () => { [setProperties, properties.pagination, properties.sorting] ); - const hostsTableColumns = useMemo( - () => buildHostsTableColumns({ time: unifiedSearchDateRange }), - [unifiedSearchDateRange] - ); - if (loading) { return ( { 'data-test-subj': 'hostsView-tableRow', }} items={items} - columns={hostsTableColumns} + columns={columns} onTableChange={onTableChange} /> ); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx deleted file mode 100644 index d7c0096e129a1..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_columns.tsx +++ /dev/null @@ -1,139 +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 { EuiBasicTableColumn } from '@elastic/eui'; -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiText } from '@elastic/eui'; -import { encode } from '@kbn/rison'; -import { TimeRange } from '@kbn/es-query'; -import type { SnapshotMetricInput, SnapshotNodeMetric } from '../../../../../common/http_api'; -import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter'; -import { CloudProviderIconWithTitle } from './cloud_provider_icon_with_title'; -import { TruncateLinkWithTooltip } from './truncate_link_with_tooltip'; - -interface HostNodeRow extends HostMetrics { - os?: string | null; - servicesOnHost?: number | null; - title: { name: string; cloudProvider?: string | null }; - name: string; -} - -export interface HostMetrics { - cpuCores: SnapshotNodeMetric; - diskLatency: SnapshotNodeMetric; - rx: SnapshotNodeMetric; - tx: SnapshotNodeMetric; - memory: SnapshotNodeMetric; - memoryTotal: SnapshotNodeMetric; -} - -const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefined | null) => - value || value === 0 ? createInventoryMetricFormatter({ type })(value) : 'N/A'; - -interface HostBuilderParams { - time: TimeRange; -} - -export const buildHostsTableColumns = ({ - time, -}: HostBuilderParams): Array> => { - const hostLinkSearch = { - _a: encode({ time: { ...time, interval: '>=1m' } }), - }; - - return [ - { - name: i18n.translate('xpack.infra.hostsViewPage.table.nameColumnHeader', { - defaultMessage: 'Name', - }), - field: 'title', - sortable: true, - truncateText: true, - render: (title: HostNodeRow['title']) => ( - - } - /> - ), - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.operatingSystemColumnHeader', { - defaultMessage: 'Operating System', - }), - field: 'os', - sortable: true, - render: (os: string) => {os}, - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.numberOfCpusColumnHeader', { - defaultMessage: '# of CPUs', - }), - field: 'cpuCores', - sortable: true, - render: (cpuCores: SnapshotNodeMetric) => ( - <>{formatMetric('cpuCores', cpuCores?.value ?? cpuCores?.max)} - ), - align: 'right', - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.diskLatencyColumnHeader', { - defaultMessage: 'Disk Latency (avg.)', - }), - field: 'diskLatency.avg', - sortable: true, - render: (avg: number) => <>{formatMetric('diskLatency', avg)}, - align: 'right', - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.averageTxColumnHeader', { - defaultMessage: 'TX (avg.)', - }), - field: 'tx.avg', - sortable: true, - render: (avg: number) => <>{formatMetric('tx', avg)}, - align: 'right', - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.averageRxColumnHeader', { - defaultMessage: 'RX (avg.)', - }), - field: 'rx.avg', - sortable: true, - render: (avg: number) => <>{formatMetric('rx', avg)}, - align: 'right', - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.averageMemoryTotalColumnHeader', { - defaultMessage: 'Memory total (avg.)', - }), - field: 'memoryTotal.avg', - sortable: true, - render: (avg: number) => <>{formatMetric('memoryTotal', avg)}, - align: 'right', - }, - { - name: i18n.translate('xpack.infra.hostsViewPage.table.averageMemoryUsageColumnHeader', { - defaultMessage: 'Memory usage (avg.)', - }), - field: 'memory.avg', - sortable: true, - render: (avg: number) => <>{formatMetric('memory', avg)}, - align: 'right', - }, - ]; -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_entry_title.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_entry_title.tsx new file mode 100644 index 0000000000000..9f0a8f9260265 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table_entry_title.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, EuiToolTip, IconType } from '@elastic/eui'; +import { TimeRange } from '@kbn/es-query'; +import { useLinkProps } from '@kbn/observability-plugin/public'; +import { encode } from '@kbn/rison'; +import type { CloudProvider, HostNodeRow } from '../hooks/use_hosts_table'; + +const cloudIcons: Record = { + gcp: 'logoGCP', + aws: 'logoAWS', + azure: 'logoAzure', + unknownProvider: 'cloudSunny', +}; + +interface HostsTableEntryTitleProps { + onClick: () => void; + time: TimeRange; + title: HostNodeRow['title']; +} + +export const HostsTableEntryTitle = ({ onClick, time, title }: HostsTableEntryTitleProps) => { + const { name, cloudProvider } = title; + + const link = useLinkProps({ + app: 'metrics', + pathname: `/detail/host/${name}`, + search: { + _a: encode({ time: { ...time, interval: '>=1m' } }), + }, + }); + + const iconType = (cloudProvider && cloudIcons[cloudProvider]) || cloudIcons.unknownProvider; + const providerName = cloudProvider ?? 'Unknown'; + + return ( + + + + + + + + + + {name} + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx index 3e82c254b0f7c..9d49e1fad782e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/kpi_charts/kpi_chart.tsx @@ -120,6 +120,6 @@ export const KPIChart = ({ const KPIChartStyled = styled(Chart)` .echMetric { - border-radius: 5px; + border-radius: ${(p) => p.theme.eui.euiBorderRadius}; } `; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/truncate_link_with_tooltip.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/truncate_link_with_tooltip.tsx deleted file mode 100644 index a7b94dd2d92b5..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/truncate_link_with_tooltip.tsx +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiToolTip, EuiLink } from '@elastic/eui'; -import React from 'react'; -import { LinkDescriptor, useLinkProps } from '@kbn/observability-plugin/public'; - -interface Props { - text: string; - linkProps: LinkDescriptor; -} - -export function TruncateLinkWithTooltip(props: Props) { - const { text, linkProps } = props; - - const link = useLinkProps(linkProps); - - return ( -
- - - {text} - - -
- ); -} diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts index aa45c44cc4e36..d85b367ba6fce 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.test.ts @@ -128,8 +128,10 @@ describe('useHostTable hook', () => { }, }, ]; - const result = renderHook(() => useHostsTable(nodes)); + const time = { from: 'now-15m', to: 'now', interval: '>=1m' }; - expect(result.result.current).toStrictEqual(items); + const { result } = renderHook(() => useHostsTable(nodes, { time })); + + expect(result.current.items).toStrictEqual(items); }); }); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.ts deleted file mode 100644 index f53b0b890041c..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.ts +++ /dev/null @@ -1,31 +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 { useMemo } from 'react'; -import type { SnapshotNode, SnapshotNodeMetric } from '../../../../../common/http_api'; -import { HostMetrics } from '../components/hosts_table_columns'; - -type MappedMetrics = Record; - -export const useHostsTable = (nodes: SnapshotNode[]) => { - const items = useMemo(() => { - return nodes.map(({ metrics, path, name }) => ({ - name, - os: path.at(-1)?.os ?? '-', - title: { - name, - cloudProvider: path.at(-1)?.cloudProvider ?? null, - }, - ...metrics.reduce((data, metric) => { - data[metric.name as keyof HostMetrics] = metric; - return data; - }, {} as MappedMetrics), - })); - }, [nodes]); - - return items; -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx new file mode 100644 index 0000000000000..ac9096504d071 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_table.tsx @@ -0,0 +1,196 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { EuiBasicTableColumn, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { TimeRange } from '@kbn/es-query'; + +import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; +import { createInventoryMetricFormatter } from '../../inventory_view/lib/create_inventory_metric_formatter'; +import { HostsTableEntryTitle } from '../components/hosts_table_entry_title'; +import type { + SnapshotNode, + SnapshotNodeMetric, + SnapshotMetricInput, +} from '../../../../../common/http_api'; + +/** + * Columns and items types + */ +export type CloudProvider = 'gcp' | 'aws' | 'azure' | 'unknownProvider'; + +type HostMetric = 'cpuCores' | 'diskLatency' | 'rx' | 'tx' | 'memory' | 'memoryTotal'; + +type HostMetrics = Record; + +export interface HostNodeRow extends HostMetrics { + os?: string | null; + servicesOnHost?: number | null; + title: { name: string; cloudProvider?: CloudProvider | null }; + name: string; +} + +// type MappedMetrics = Record; + +interface HostTableParams { + time: TimeRange; +} + +/** + * Helper functions + */ +const formatMetric = (type: SnapshotMetricInput['type'], value: number | undefined | null) => { + return value || value === 0 ? createInventoryMetricFormatter({ type })(value) : 'N/A'; +}; + +const buildItemsList = (nodes: SnapshotNode[]) => { + return nodes.map(({ metrics, path, name }) => ({ + name, + os: path.at(-1)?.os ?? '-', + title: { + name, + cloudProvider: path.at(-1)?.cloudProvider ?? null, + }, + ...metrics.reduce((data, metric) => { + data[metric.name as HostMetric] = metric; + return data; + }, {} as HostMetrics), + })) as HostNodeRow[]; +}; + +/** + * Columns translations + */ +const titleLabel = i18n.translate('xpack.infra.hostsViewPage.table.nameColumnHeader', { + defaultMessage: 'Name', +}); + +const osLabel = i18n.translate('xpack.infra.hostsViewPage.table.operatingSystemColumnHeader', { + defaultMessage: 'Operating System', +}); + +const cpuCountLabel = i18n.translate('xpack.infra.hostsViewPage.table.numberOfCpusColumnHeader', { + defaultMessage: '# of CPUs', +}); + +const diskLatencyLabel = i18n.translate('xpack.infra.hostsViewPage.table.diskLatencyColumnHeader', { + defaultMessage: 'Disk Latency (avg.)', +}); + +const averageTXLabel = i18n.translate('xpack.infra.hostsViewPage.table.averageTxColumnHeader', { + defaultMessage: 'TX (avg.)', +}); + +const averageRXLabel = i18n.translate('xpack.infra.hostsViewPage.table.averageRxColumnHeader', { + defaultMessage: 'RX (avg.)', +}); + +const averageTotalMemoryLabel = i18n.translate( + 'xpack.infra.hostsViewPage.table.averageMemoryTotalColumnHeader', + { + defaultMessage: 'Memory total (avg.)', + } +); + +const averageMemoryUsageLabel = i18n.translate( + 'xpack.infra.hostsViewPage.table.averageMemoryUsageColumnHeader', + { + defaultMessage: 'Memory usage (avg.)', + } +); + +/** + * Build a table columns and items starting from the snapshot nodes. + */ +export const useHostsTable = (nodes: SnapshotNode[], { time }: HostTableParams) => { + const { + services: { telemetry }, + } = useKibanaContextForPlugin(); + + const reportHostEntryClick = useCallback( + ({ name, cloudProvider }: HostNodeRow['title']) => { + telemetry.reportHostEntryClicked({ + hostname: name, + cloud_provider: cloudProvider, + }); + }, + [telemetry] + ); + + const items = useMemo(() => buildItemsList(nodes), [nodes]); + + const columns: Array> = useMemo( + () => [ + { + name: titleLabel, + field: 'title', + sortable: true, + truncateText: true, + render: (title: HostNodeRow['title']) => ( + reportHostEntryClick(title)} + /> + ), + }, + { + name: osLabel, + field: 'os', + sortable: true, + render: (os: string) => {os}, + }, + { + name: cpuCountLabel, + field: 'cpuCores', + sortable: true, + render: (cpuCores: SnapshotNodeMetric) => + formatMetric('cpuCores', cpuCores?.value ?? cpuCores?.max), + align: 'right', + }, + { + name: diskLatencyLabel, + field: 'diskLatency.avg', + sortable: true, + render: (avg: number) => formatMetric('diskLatency', avg), + align: 'right', + }, + { + name: averageTXLabel, + field: 'tx.avg', + sortable: true, + render: (avg: number) => formatMetric('tx', avg), + align: 'right', + }, + { + name: averageRXLabel, + field: 'rx.avg', + sortable: true, + render: (avg: number) => formatMetric('rx', avg), + align: 'right', + }, + { + name: averageTotalMemoryLabel, + field: 'memoryTotal.avg', + sortable: true, + render: (avg: number) => formatMetric('memoryTotal', avg), + align: 'right', + }, + { + name: averageMemoryUsageLabel, + field: 'memory.avg', + sortable: true, + render: (avg: number) => formatMetric('memory', avg), + align: 'right', + }, + ], + [reportHostEntryClick, time] + ); + + return { columns, items }; +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts index 671b3484356f1..fa259784a3d72 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_hosts_view.ts @@ -32,32 +32,34 @@ export const INITAL_VALUE = { export const useHostsView = () => { const { sourceId } = useSourceContext(); - const { buildQuery, dateRangeTimestamp } = useUnifiedSearchContext(); + const { buildQuery, getDateRangeAsTimestamp } = useUnifiedSearchContext(); const [hostViewState, setHostViewState] = useState(INITAL_VALUE); const baseRequest = useMemo(() => { const esQuery = buildQuery(); + const { from, to } = getDateRangeAsTimestamp(); + const snapshotRequest: UseSnapshotRequest = { filterQuery: esQuery ? JSON.stringify(esQuery) : null, metrics: [], groupBy: [], nodeType: 'host', sourceId, - currentTime: dateRangeTimestamp.to, + currentTime: to, includeTimeseries: false, sendRequestImmediately: true, timerange: { interval: '1m', - from: dateRangeTimestamp.from, - to: dateRangeTimestamp.to, + from, + to, ignoreLookback: true, }, // The user might want to click on the submit button without changing the filters - // This makes sure all child componets will re-render. + // This makes sure all child components will re-render. requestTs: Date.now(), }; return snapshotRequest; - }, [buildQuery, dateRangeTimestamp.from, dateRangeTimestamp.to, sourceId]); + }, [buildQuery, getDateRangeAsTimestamp, sourceId]); return { baseRequest, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts index c6753345d1705..78e4f9bfeee4b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -11,17 +11,30 @@ import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; import type { SavedQuery } from '@kbn/data-plugin/public'; import { debounce } from 'lodash'; import deepEqual from 'fast-deep-equal'; +import { telemetryTimeRangeFormatter } from '../../../../../common/formatters/telemetry_time_range'; import type { InfraClientStartDeps } from '../../../../types'; import { useMetricsDataViewContext } from './use_data_view'; import { useSyncKibanaTimeFilterTime } from '../../../../hooks/use_kibana_timefilter_time'; -import { useHostsUrlState, INITIAL_DATE_RANGE } from './use_unified_search_url_state'; +import { useHostsUrlState, INITIAL_DATE_RANGE, HostsState } from './use_unified_search_url_state'; + +const buildQuerySubmittedPayload = (hostState: HostsState) => { + const { panelFilters, filters, dateRangeTimestamp, query: queryObj } = hostState; + + return { + control_filters: panelFilters.map((filter) => JSON.stringify(filter)), + filters: filters.map((filter) => JSON.stringify(filter)), + interval: telemetryTimeRangeFormatter(dateRangeTimestamp.to - dateRangeTimestamp.from), + query: queryObj.query, + }; +}; export const useUnifiedSearch = () => { - const { state, dispatch, getRangeInTimestamp, getTime } = useHostsUrlState(); + const { state, dispatch, getTime, getDateRangeAsTimestamp } = useHostsUrlState(); const { metricsDataView } = useMetricsDataViewContext(); const { services } = useKibana(); const { data: { query: queryManager }, + telemetry, } = services; useSyncKibanaTimeFilterTime(INITIAL_DATE_RANGE, { @@ -62,6 +75,11 @@ export const useUnifiedSearch = () => { }; }); + // Track telemetry event on query/filter/date changes + useEffect(() => { + telemetry.reportHostsViewQuerySubmitted(buildQuerySubmittedPayload(state)); + }, [state, telemetry]); + const onSubmit = useCallback( (data?: { query?: Query; @@ -78,12 +96,11 @@ export const useUnifiedSearch = () => { query, filters, dateRange: newDateRange, - dateRangeTimestamp: getRangeInTimestamp(newDateRange), panelFilters, }, }); }, - [getTime, dispatch, getRangeInTimestamp] + [getTime, dispatch] ); // This won't prevent onSubmit from being fired twice when `clear filters` is clicked, @@ -131,9 +148,9 @@ export const useUnifiedSearch = () => { buildQuery, clearSavedQuery, controlPanelFilters: state.panelFilters, - dateRangeTimestamp: state.dateRangeTimestamp, onSubmit: debounceOnSubmit, saveQuery, + getDateRangeAsTimestamp, unifiedSearchQuery: state.query, unifiedSearchDateRange: state.dateRange, unifiedSearchFilters: state.filters, diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts index e50fa2fa76cc4..1a19f21626d82 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts @@ -6,7 +6,6 @@ */ import { useCallback, useEffect, useReducer } from 'react'; -import { TimeRange } from '@kbn/es-query'; import DateMath from '@kbn/datemath'; import deepEqual from 'fast-deep-equal'; import * as rt from 'io-ts'; @@ -23,25 +22,19 @@ const DEFAULT_QUERY = { query: '', }; const DEFAULT_FROM_MINUTES_VALUE = 15; -const INITIAL_DATE = new Date(); -export const INITIAL_DATE_RANGE = { from: `now-${DEFAULT_FROM_MINUTES_VALUE}m`, to: 'now' }; -const CALCULATED_DATE_RANGE_TO = INITIAL_DATE.getTime(); const DEFAULT_FROM_IN_MILLISECONDS = DEFAULT_FROM_MINUTES_VALUE * 60000; -const CALCULATED_DATE_RANGE_FROM = new Date( - CALCULATED_DATE_RANGE_TO - DEFAULT_FROM_IN_MILLISECONDS -).getTime(); + +export const INITIAL_DATE_RANGE = { from: `now-${DEFAULT_FROM_MINUTES_VALUE}m`, to: 'now' }; + +const getDefaultFromTimestamp = () => Date.now() - DEFAULT_FROM_IN_MILLISECONDS; +const getDefaultToTimestamp = () => Date.now(); const INITIAL_HOSTS_STATE: HostsState = { query: DEFAULT_QUERY, filters: [], panelFilters: [], // for unified search - dateRange: { ...INITIAL_DATE_RANGE }, - // for useSnapshot - dateRangeTimestamp: { - from: CALCULATED_DATE_RANGE_FROM, - to: CALCULATED_DATE_RANGE_TO, - }, + dateRange: INITIAL_DATE_RANGE, }; type Action = @@ -84,15 +77,12 @@ export const useHostsUrlState = () => { const [state, dispatch] = useReducer(reducer, urlState); - const getRangeInTimestamp = useCallback(({ from, to }: TimeRange) => { - const fromTS = DateMath.parse(from)?.valueOf() ?? CALCULATED_DATE_RANGE_FROM; - const toTS = DateMath.parse(to)?.valueOf() ?? CALCULATED_DATE_RANGE_TO; + const getDateRangeAsTimestamp = useCallback(() => { + const from = DateMath.parse(state.dateRange.from)?.valueOf() ?? getDefaultFromTimestamp(); + const to = DateMath.parse(state.dateRange.to)?.valueOf() ?? getDefaultToTimestamp(); - return { - from: fromTS, - to: toTS, - }; - }, []); + return { from, to }; + }, [state.dateRange]); useEffect(() => { if (!deepEqual(state, urlState)) { @@ -102,7 +92,7 @@ export const useHostsUrlState = () => { return { dispatch, - getRangeInTimestamp, + getDateRangeAsTimestamp, getTime, state, }; @@ -144,17 +134,11 @@ const StringDateRangeRT = rt.type({ to: rt.string, }); -const DateRangeRT = rt.type({ - from: rt.number, - to: rt.number, -}); - const HostsStateRT = rt.type({ filters: HostsFiltersRT, panelFilters: HostsFiltersRT, query: HostsQueryStateRT, dateRange: StringDateRangeRT, - dateRangeTimestamp: DateRangeRT, }); export type HostsState = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts index 32f0e1d06cff8..bc76dbba39e10 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/lib/create_inventory_metric_formatter.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { get } from 'lodash'; +import { get, isNumber } from 'lodash'; import { SnapshotMetricType } from '../../../../../common/inventory_models/types'; import { InfraFormatterType } from '../../../../lib/lib'; import { @@ -95,7 +95,7 @@ export const createInventoryMetricFormatter = (metric: SnapshotMetricInput) => (val: string | number) => { if (SnapshotCustomMetricInputRT.is(metric)) { const formatter = createFormatterForMetric(metric); - return formatter(val); + return isNumber(val) ? formatter(val) : val; } const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count); if (val == null || !metricFormatter) { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/aggregation.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/aggregation.tsx index 7b76cf22098fb..d9117da001247 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/aggregation.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/aggregation.tsx @@ -9,6 +9,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useCallback } from 'react'; +import { xor } from 'lodash'; import { MetricsExplorerAggregation } from '../../../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; import { @@ -22,6 +23,8 @@ interface Props { onChange: (aggregation: MetricsExplorerAggregation) => void; } +type MetricsExplorerAggregationWithoutCustom = Exclude; + export const MetricsExplorerAggregationPicker = ({ options, onChange }: Props) => { const AGGREGATION_LABELS = { ['avg']: i18n.translate('xpack.infra.metricsExplorer.aggregationLables.avg', { @@ -66,14 +69,18 @@ export const MetricsExplorerAggregationPicker = ({ options, onChange }: Props) = defaultMessage: 'Select an aggregation', }); + const METRIC_EXPLORER_AGGREGATIONS_WITHOUT_CUSTOM = xor(METRIC_EXPLORER_AGGREGATIONS, [ + 'custom', + ]) as MetricsExplorerAggregationWithoutCustom[]; + return ( ({ - text: AGGREGATION_LABELS[k as MetricsExplorerAggregation], + options={METRIC_EXPLORER_AGGREGATIONS_WITHOUT_CUSTOM.map((k) => ({ + text: AGGREGATION_LABELS[k], value: k, }))} onChange={handleChange} diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts index 3857b81825c00..b6f77bfc260a4 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_formatter_for_metric.ts @@ -5,11 +5,15 @@ * 2.0. */ +import numeral from '@elastic/numeral'; import { MetricsExplorerMetric } from '../../../../../../common/http_api/metrics_explorer'; import { createFormatter } from '../../../../../../common/formatters'; import { InfraFormatterType } from '../../../../../lib/lib'; import { metricToFormat } from './metric_to_format'; export const createFormatterForMetric = (metric?: MetricsExplorerMetric) => { + if (metric?.aggregation === 'custom') { + return (input: number) => numeral(input).format('0.[0000]'); + } if (metric && metric.field) { const format = metricToFormat(metric); if (format === InfraFormatterType.bits && metric.aggregation === 'rate') { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts index 2c1be3d17f6f8..ccb1d316d73d2 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts @@ -67,13 +67,7 @@ export function useMetricsExplorerData( body: JSON.stringify({ forceInterval: options.forceInterval, dropLastBucket: options.dropLastBucket != null ? options.dropLastBucket : true, - metrics: - options.aggregation === 'count' - ? [{ aggregation: 'count' }] - : options.metrics.map((metric) => ({ - aggregation: metric.aggregation, - field: metric.field, - })), + metrics: options.aggregation === 'count' ? [{ aggregation: 'count' }] : options.metrics, groupBy: options.groupBy, afterKey, limit: options.limit, @@ -117,6 +111,7 @@ export function useMetricsExplorerData( }, onReject: (e: unknown) => { setError(e as Error); + setData(null); setLoading(false); }, }, diff --git a/x-pack/plugins/infra/public/services/telemetry/types.ts b/x-pack/plugins/infra/public/services/telemetry/types.ts index 9f92f9e614810..4ce5dea999111 100644 --- a/x-pack/plugins/infra/public/services/telemetry/types.ts +++ b/x-pack/plugins/infra/public/services/telemetry/types.ts @@ -33,12 +33,12 @@ export interface HostsViewQuerySubmittedSchema { export interface HostEntryClickedParams { hostname: string; - cloud_provider?: string; + cloud_provider?: string | null; } export interface HostEntryClickedSchema { hostname: SchemaValue; - cloud_provider: SchemaValue; + cloud_provider: SchemaValue; } export interface ITelemetryClient { diff --git a/x-pack/plugins/infra/public/types.ts b/x-pack/plugins/infra/public/types.ts index a001e2a7e9096..cf4f3a0c8b934 100644 --- a/x-pack/plugins/infra/public/types.ts +++ b/x-pack/plugins/infra/public/types.ts @@ -80,6 +80,7 @@ export interface InfraClientStartDeps { share: SharePluginStart; storage: IStorageWrapper; lens: LensPublicStart; + telemetry: ITelemetryClient; } export type InfraClientCoreSetup = CoreSetup; diff --git a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts index d35651d66c95a..ab7b0490aa901 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts @@ -20,6 +20,13 @@ export const DOCUMENT_COUNT_I18N = i18n.translate( } ); +export const CUSTOM_EQUATION_I18N = i18n.translate( + 'xpack.infra.metrics.alerting.threshold.customEquation', + { + defaultMessage: 'Custom equation', + } +); + export const stateToAlertMessage = { [AlertStates.ALERT]: i18n.translate('xpack.infra.metrics.alerting.threshold.alertState', { defaultMessage: 'ALERT', @@ -76,7 +83,7 @@ const thresholdToI18n = ([a, b]: Array) => { }); }; -const formatGroup = (group: string) => (group === UNGROUPED_FACTORY_KEY ? 'all hosts' : group); +const formatGroup = (group: string) => (group === UNGROUPED_FACTORY_KEY ? '' : ` for ${group}`); export const buildFiredAlertReason: (alertResult: { group: string; @@ -89,7 +96,7 @@ export const buildFiredAlertReason: (alertResult: { }) => string = ({ group, metric, comparator, threshold, currentValue, timeSize, timeUnit }) => i18n.translate('xpack.infra.metrics.alerting.threshold.firedAlertReason', { defaultMessage: - '{metric} is {currentValue} in the last {duration} for {group}. Alert when {comparator} {threshold}.', + '{metric} is {currentValue} in the last {duration}{group}. Alert when {comparator} {threshold}.', values: { group: formatGroup(group), metric, @@ -271,3 +278,19 @@ export const tagsActionVariableDescription = i18n.translate( defaultMessage: 'List of tags associated with the entity where this alert triggered.', } ); + +export const originalAlertStateActionVariableDescription = i18n.translate( + 'xpack.infra.metrics.alerting.originalAlertStateActionVariableDescription', + { + defaultMessage: + 'The state of the alert before it recovered. This is only available in the recovery context', + } +); + +export const originalAlertStateWasActionVariableDescription = i18n.translate( + 'xpack.infra.metrics.alerting.originalAlertStateWasWARNINGActionVariableDescription', + { + defaultMessage: + 'Boolean value of the state of the alert before it recovered. This can be used for template conditions. This is only available in the recovery context', + } +); diff --git a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts index 970d3779a2e25..b6c0b5578c6a0 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts @@ -237,7 +237,7 @@ export const flattenAdditionalContext = ( }; export const getContextForRecoveredAlerts = ( - alertHits: AdditionalContext | undefined | null + alertHits: AdditionalContext[] | undefined | null ): AdditionalContext => { const alertHitsSource = alertHits && alertHits.length > 0 ? unflattenObject(alertHits[0]._source) : undefined; diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index b6e0c5dd578e5..ffd0a7e563339 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ALERT_REASON } from '@kbn/rule-data-utils'; +import { ALERT_REASON, ALERT_ACTION_GROUP } from '@kbn/rule-data-utils'; import { first, get } from 'lodash'; import { ActionGroup, @@ -15,6 +15,7 @@ import { AlertInstanceState as AlertState, } from '@kbn/alerting-plugin/common'; import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; +import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; import { AlertStates, InventoryMetricThresholdParams } from '../../../../common/alerting/metrics'; import { createFormatter } from '../../../../common/formatters'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; @@ -45,6 +46,11 @@ type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< typeof FIRED_ACTIONS | typeof WARNING_ACTIONS >; +export const FIRED_ACTIONS_ID = 'metrics.inventory_threshold.fired'; +export const WARNING_ACTIONS_ID = 'metrics.inventory_threshold.warning'; + +type InventoryThrehsoldActionGroup = typeof FIRED_ACTIONS_ID | typeof WARNING_ACTIONS_ID; + export type InventoryMetricThresholdRuleTypeState = RuleTypeState; // no specific state used export type InventoryMetricThresholdAlertState = AlertState; // no specific state used export type InventoryMetricThresholdAlertContext = AlertContext; // no specific instance context used @@ -57,6 +63,7 @@ type InventoryMetricThresholdAlert = Alert< type InventoryMetricThresholdAlertFactory = ( id: string, reason: string, + actionGroup: InventoryThrehsoldActionGroup, additionalContext?: AdditionalContext | null, threshold?: number | undefined, value?: number | undefined @@ -90,11 +97,17 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = getAlertUuid, getAlertByAlertUuid, } = services; - const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason, additionalContext) => + const alertFactory: InventoryMetricThresholdAlertFactory = ( + id, + reason, + actionGroup, + additionalContext + ) => alertWithLifecycle({ id, fields: { [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, ...flattenAdditionalContext(additionalContext), }, }); @@ -107,7 +120,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = logger.error(e.message); const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); const indexedStartedDate = getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); @@ -212,11 +225,11 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = } if (reason) { const actionGroupId = - nextState === AlertStates.WARNING ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; + nextState === AlertStates.WARNING ? WARNING_ACTIONS_ID : FIRED_ACTIONS_ID; const additionalContext = results && results.length > 0 ? results[0][group].context : null; - const alert = alertFactory(group, reason, additionalContext); + const alert = alertFactory(group, reason, actionGroupId, additionalContext); const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); const alertUuid = getAlertUuid(group); @@ -255,6 +268,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const alertUuid = getAlertUuid(recoveredAlertId); const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; const additionalContext = getContextForRecoveredAlerts(alertHits); + const originalActionGroup = getOriginalActionGroup(alertHits); alert.setContext({ alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), @@ -270,6 +284,9 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = timestamp: indexedStartedDate, spaceId, }), + originalAlertState: translateActionGroupToAlertState(originalActionGroup), + originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS_ID, + originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS_ID, ...additionalContext, }); } @@ -335,14 +352,12 @@ const mapToConditionsLookup = ( {} ); -export const FIRED_ACTIONS_ID = 'metrics.inventory_threshold.fired'; export const FIRED_ACTIONS: ActionGroup = { id: FIRED_ACTIONS_ID, name: i18n.translate('xpack.infra.metrics.alerting.inventory.threshold.fired', { defaultMessage: 'Alert', }), }; -export const WARNING_ACTIONS_ID = 'metrics.inventory_threshold.warning'; export const WARNING_ACTIONS = { id: WARNING_ACTIONS_ID, name: i18n.translate('xpack.infra.metrics.alerting.threshold.warning', { @@ -350,6 +365,17 @@ export const WARNING_ACTIONS = { }), }; +const translateActionGroupToAlertState = ( + actionGroupId: string | undefined +): string | undefined => { + if (actionGroupId === FIRED_ACTIONS.id) { + return stateToAlertMessage[AlertStates.ALERT]; + } + if (actionGroupId === WARNING_ACTIONS.id) { + return stateToAlertMessage[AlertStates.WARNING]; + } +}; + const formatMetric = (metric: SnapshotMetricType, value: number) => { const metricFormatter = get(METRIC_FORMATTERS, metric, METRIC_FORMATTERS.count); if (isNaN(value)) { diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index a5ba2c32ada6e..b0b8a13562feb 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -33,6 +33,8 @@ import { labelsActionVariableDescription, metricActionVariableDescription, orchestratorActionVariableDescription, + originalAlertStateActionVariableDescription, + originalAlertStateWasActionVariableDescription, reasonActionVariableDescription, tagsActionVariableDescription, thresholdActionVariableDescription, @@ -124,6 +126,15 @@ export async function registerMetricInventoryThresholdRuleType( { name: 'orchestrator', description: orchestratorActionVariableDescription }, { name: 'labels', description: labelsActionVariableDescription }, { name: 'tags', description: tagsActionVariableDescription }, + { name: 'originalAlertState', description: originalAlertStateActionVariableDescription }, + { + name: 'originalAlertStateWasALERT', + description: originalAlertStateWasActionVariableDescription, + }, + { + name: 'originalAlertStateWasWARNING', + description: originalAlertStateWasActionVariableDescription, + }, ], }, getSummarizedAlerts: libs.metricsRules.createGetSummarizedAlerts(), diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_bucket_selector.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_bucket_selector.ts index d078cee95e45e..a458104a7400a 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_bucket_selector.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_bucket_selector.ts @@ -32,12 +32,14 @@ export const createBucketSelector = ( const isCount = condition.aggType === Aggregators.COUNT; const isRate = condition.aggType === Aggregators.RATE; const bucketPath = isCount - ? 'currentPeriod>_count' + ? "currentPeriod['all']>_count" : isRate ? `aggregatedValue` : isPercentile - ? `currentPeriod>aggregatedValue[${condition.aggType === Aggregators.P95 ? '95' : '99'}]` - : 'currentPeriod>aggregatedValue'; + ? `currentPeriod[\'all\']>aggregatedValue[${ + condition.aggType === Aggregators.P95 ? '95' : '99' + }]` + : "currentPeriod['all']>aggregatedValue"; const shouldWarn = hasWarn ? { @@ -74,7 +76,7 @@ export const createBucketSelector = ( bucket_script: { buckets_path: { lastPeriod: 'lastPeriod>_count', - currentPeriod: 'currentPeriod>_count', + currentPeriod: "currentPeriod['all']>_count", }, script: 'params.lastPeriod > 0 && params.currentPeriod < 1 ? 1 : 0', }, @@ -83,7 +85,7 @@ export const createBucketSelector = ( bucket_script: { buckets_path: { lastPeriod: 'lastPeriod>_count', - currentPeriod: 'currentPeriod>_count', + currentPeriod: "currentPeriod['all']>_count", }, script: 'params.lastPeriod < 1 && params.currentPeriod > 0 ? 1 : 0', }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_rate_aggregation.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_rate_aggregation.ts index 2fdb8f5c6b834..e6b24e334a745 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_rate_aggregation.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/create_rate_aggregation.ts @@ -21,8 +21,8 @@ export const createRateAggsBucketScript = ( [id]: { bucket_script: { buckets_path: { - first: `currentPeriod>${id}_first_bucket.maxValue`, - second: `currentPeriod>${id}_second_bucket.maxValue`, + first: `currentPeriod['all']>${id}_first_bucket.maxValue`, + second: `currentPeriod['all']>${id}_second_bucket.maxValue`, }, script: `params.second > 0.0 && params.first > 0.0 && params.second > params.first ? (params.second - params.first) / ${intervalInSeconds}: null`, }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts index b9c7817eb5be4..7125f18aa2a8d 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/lib/evaluate_rule.ts @@ -11,7 +11,7 @@ import type { Logger } from '@kbn/logging'; import { MetricExpressionParams } from '../../../../../common/alerting/metrics'; import { InfraSource } from '../../../../../common/source_configuration/source_configuration'; import { getIntervalInSeconds } from '../../../../../common/utils/get_interval_in_seconds'; -import { DOCUMENT_COUNT_I18N } from '../../common/messages'; +import { CUSTOM_EQUATION_I18N, DOCUMENT_COUNT_I18N } from '../../common/messages'; import { createTimerange } from './create_timerange'; import { getData } from './get_data'; import { checkMissingGroups, MissingGroupsRecord } from './check_missing_group'; @@ -101,7 +101,14 @@ export const evaluateRule = async ( ) => { return { currentPeriod: { - filter: { - range: { - [TIMESTAMP_FIELD]: { - gte: moment(timeframe.start).toISOString(), - lte: moment(timeframe.end).toISOString(), + filters: { + filters: { + all: { + range: { + [TIMESTAMP_FIELD]: { + gte: moment(timeframe.start).toISOString(), + lte: moment(timeframe.end).toISOString(), + }, + }, }, }, }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 409dede158515..92fbc186dce5f 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -878,8 +878,6 @@ describe('The metric threshold alert type', () => { expect(reasons[1]).toContain('Alert when >= 3'); expect(reasons[0]).toContain('in the last 1 min'); expect(reasons[1]).toContain('in the last 1 min'); - expect(reasons[0]).toContain('for all hosts'); - expect(reasons[1]).toContain('for all hosts'); }); }); describe('querying with the count aggregator', () => { @@ -1659,9 +1657,7 @@ describe('The metric threshold alert type', () => { const { action } = mostRecentAction(instanceID); expect(action.group).toBe('*'); - expect(action.reason).toBe( - 'test.metric.1 is 2.5 in the last 1 min for all hosts. Alert when > 2.49.' - ); + expect(action.reason).toBe('test.metric.1 is 2.5 in the last 1 min. Alert when > 2.49.'); }); test('reports expected warning values to the action context for percentage metric', async () => { @@ -1675,9 +1671,7 @@ describe('The metric threshold alert type', () => { const { action } = mostRecentAction(instanceID); expect(action.group).toBe('*'); - expect(action.reason).toBe( - 'system.cpu.user.pct is 82% in the last 1 min for all hosts. Alert when > 81%.' - ); + expect(action.reason).toBe('system.cpu.user.pct is 82% in the last 1 min. Alert when > 81%.'); }); }); }); @@ -1823,18 +1817,19 @@ declare global { } } -const baseNonCountCriterion: Pick< - NonCountMetricExpressionParams, - 'aggType' | 'metric' | 'timeSize' | 'timeUnit' -> = { +const baseNonCountCriterion = { aggType: Aggregators.AVERAGE, metric: 'test.metric.1', timeSize: 1, timeUnit: 'm', -}; + threshold: [0], + comparator: Comparator.GT, +} as NonCountMetricExpressionParams; -const baseCountCriterion: Pick = { +const baseCountCriterion = { aggType: Aggregators.COUNT, timeSize: 1, timeUnit: 'm', -}; + threshold: [0], + comparator: Comparator.GT, +} as CountMetricExpressionParams; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 07af8bbcafe22..cd0b0f48f36d4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ALERT_REASON } from '@kbn/rule-data-utils'; +import { ALERT_ACTION_GROUP, ALERT_REASON } from '@kbn/rule-data-utils'; import { isEqual } from 'lodash'; import { ActionGroupIdsOf, @@ -16,6 +16,7 @@ import { } from '@kbn/alerting-plugin/common'; import { Alert, RuleTypeState } from '@kbn/alerting-plugin/server'; import { TimeUnitChar } from '@kbn/observability-plugin/common/utils/formatters/duration'; +import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; import { AlertStates, Comparator } from '../../../../common/alerting/metrics'; import { createFormatter } from '../../../../common/formatters'; import { InfraBackendLibs } from '../../infra_types'; @@ -53,8 +54,18 @@ export type MetricThresholdRuleTypeState = RuleTypeState & { export type MetricThresholdAlertState = AlertState; // no specific instace state used export type MetricThresholdAlertContext = AlertContext; // no specific instace state used +export const FIRED_ACTIONS_ID = 'metrics.threshold.fired'; +export const WARNING_ACTIONS_ID = 'metrics.threshold.warning'; +export const NO_DATA_ACTIONS_ID = 'metrics.threshold.nodata'; + +type MetricThresholdActionGroup = + | typeof FIRED_ACTIONS_ID + | typeof WARNING_ACTIONS_ID + | typeof NO_DATA_ACTIONS_ID + | typeof RecoveredActionGroup.id; + type MetricThresholdAllowedActionGroups = ActionGroupIdsOf< - typeof FIRED_ACTIONS | typeof WARNING_ACTIONS + typeof FIRED_ACTIONS | typeof WARNING_ACTIONS | typeof NO_DATA_ACTIONS >; type MetricThresholdAlert = Alert< @@ -66,6 +77,7 @@ type MetricThresholdAlert = Alert< type MetricThresholdAlertFactory = ( id: string, reason: string, + actionGroup: MetricThresholdActionGroup, additionalContext?: AdditionalContext | null, threshold?: number | undefined, value?: number | undefined @@ -101,11 +113,17 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const { alertWithLifecycle, savedObjectsClient, getAlertUuid, getAlertByAlertUuid } = services; - const alertFactory: MetricThresholdAlertFactory = (id, reason, additionalContext) => + const alertFactory: MetricThresholdAlertFactory = ( + id, + reason, + actionGroup, + additionalContext + ) => alertWithLifecycle({ id, fields: { [ALERT_REASON]: reason, + [ALERT_ACTION_GROUP]: actionGroup, ...flattenAdditionalContext(additionalContext), }, }); @@ -127,9 +145,9 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => } catch (e) { logger.error(e.message); const timestamp = startedAt.toISOString(); - const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able + const actionGroupId = FIRED_ACTIONS_ID; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason, actionGroupId); const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); alert.scheduleActions(actionGroupId, { @@ -258,14 +276,14 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => if (reason) { const timestamp = startedAt.toISOString(); - const actionGroupId = + const actionGroupId: MetricThresholdActionGroup = nextState === AlertStates.OK ? RecoveredActionGroup.id : nextState === AlertStates.NO_DATA - ? NO_DATA_ACTIONS.id + ? NO_DATA_ACTIONS_ID : nextState === AlertStates.WARNING - ? WARNING_ACTIONS.id - : FIRED_ACTIONS.id; + ? WARNING_ACTIONS_ID + : FIRED_ACTIONS_ID; const additionalContext = hasAdditionalContext(params.groupBy, validGroupByForContext) ? alertResults && alertResults.length > 0 @@ -273,7 +291,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => : null : null; - const alert = alertFactory(`${group}`, reason, additionalContext); + const alert = alertFactory(`${group}`, reason, actionGroupId, additionalContext); const alertUuid = getAlertUuid(group); scheduledActionsCount++; @@ -313,6 +331,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const alertHits = alertUuid ? await getAlertByAlertUuid(alertUuid) : undefined; const additionalContext = getContextForRecoveredAlerts(alertHits); + const originalActionGroup = getOriginalActionGroup(alertHits); alert.setContext({ alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), @@ -323,6 +342,12 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => timestamp: startedAt.toISOString(), threshold: mapToConditionsLookup(criteria, (c) => c.threshold), viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), + + originalAlertState: translateActionGroupToAlertState(originalActionGroup), + originalAlertStateWasALERT: originalActionGroup === FIRED_ACTIONS.id, + originalAlertStateWasWARNING: originalActionGroup === WARNING_ACTIONS.id, + // eslint-disable-next-line @typescript-eslint/naming-convention + originalAlertStateWasNO_DATA: originalActionGroup === NO_DATA_ACTIONS.id, ...additionalContext, }); } @@ -360,6 +385,20 @@ export const NO_DATA_ACTIONS = { }), }; +const translateActionGroupToAlertState = ( + actionGroupId: string | undefined +): string | undefined => { + if (actionGroupId === FIRED_ACTIONS.id) { + return stateToAlertMessage[AlertStates.ALERT]; + } + if (actionGroupId === WARNING_ACTIONS.id) { + return stateToAlertMessage[AlertStates.WARNING]; + } + if (actionGroupId === NO_DATA_ACTIONS.id) { + return stateToAlertMessage[AlertStates.NO_DATA]; + } +}; + const mapToConditionsLookup = ( list: any[], mapFn: (value: any, index: number, array: any[]) => unknown diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts index 55e2379bcf19a..b9d069db244fb 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts @@ -23,6 +23,8 @@ import { labelsActionVariableDescription, metricActionVariableDescription, orchestratorActionVariableDescription, + originalAlertStateActionVariableDescription, + originalAlertStateWasActionVariableDescription, reasonActionVariableDescription, tagsActionVariableDescription, thresholdActionVariableDescription, @@ -68,12 +70,42 @@ export async function registerMetricThresholdRuleType( ...baseCriterion, metric: schema.string(), aggType: oneOfLiterals(METRIC_EXPLORER_AGGREGATIONS), + customMetrics: schema.never(), + equation: schema.never(), + label: schema.never(), }); const countCriterion = schema.object({ ...baseCriterion, aggType: schema.literal('count'), metric: schema.never(), + customMetrics: schema.never(), + equation: schema.never(), + label: schema.never(), + }); + + const customCriterion = schema.object({ + ...baseCriterion, + aggType: schema.literal('custom'), + metric: schema.never(), + customMetrics: schema.arrayOf( + schema.oneOf([ + schema.object({ + name: schema.string(), + aggType: oneOfLiterals(['avg', 'sum', 'max', 'min', 'cardinality']), + field: schema.string(), + filter: schema.never(), + }), + schema.object({ + name: schema.string(), + aggType: schema.literal('count'), + filter: schema.maybe(schema.string()), + field: schema.never(), + }), + ]) + ), + equation: schema.maybe(schema.string()), + label: schema.maybe(schema.string()), }); alertingPlugin.registerType({ @@ -84,7 +116,9 @@ export async function registerMetricThresholdRuleType( validate: { params: schema.object( { - criteria: schema.arrayOf(schema.oneOf([countCriterion, nonCountCriterion])), + criteria: schema.arrayOf( + schema.oneOf([countCriterion, nonCountCriterion, customCriterion]) + ), groupBy: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), filterQuery: schema.maybe( schema.string({ @@ -124,6 +158,19 @@ export async function registerMetricThresholdRuleType( { name: 'orchestrator', description: orchestratorActionVariableDescription }, { name: 'labels', description: labelsActionVariableDescription }, { name: 'tags', description: tagsActionVariableDescription }, + { name: 'originalAlertState', description: originalAlertStateActionVariableDescription }, + { + name: 'originalAlertStateWasALERT', + description: originalAlertStateWasActionVariableDescription, + }, + { + name: 'originalAlertStateWasWARNING', + description: originalAlertStateWasActionVariableDescription, + }, + { + name: 'originalAlertStateWasNO_DATA', + description: originalAlertStateWasActionVariableDescription, + }, ], }, producer: 'infrastructure', diff --git a/x-pack/plugins/infra/server/lib/create_custom_metrics_aggregations.ts b/x-pack/plugins/infra/server/lib/create_custom_metrics_aggregations.ts new file mode 100644 index 0000000000000..862ab7c2d2961 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/create_custom_metrics_aggregations.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { isEmpty } from 'lodash'; +import { MetricExpressionCustomMetric } from '../../common/alerting/metrics'; +import { MetricsExplorerCustomMetric } from '../../common/http_api'; + +const isMetricExpressionCustomMetric = ( + subject: MetricsExplorerCustomMetric | MetricExpressionCustomMetric +): subject is MetricExpressionCustomMetric => { + return (subject as MetricExpressionCustomMetric).aggType != null; +}; + +export const createCustomMetricsAggregations = ( + id: string, + customMetrics: Array, + equation?: string +) => { + const bucketsPath: { [id: string]: string } = {}; + const metricAggregations = customMetrics.reduce((acc, metric) => { + const key = `${id}_${metric.name}`; + const aggregation = isMetricExpressionCustomMetric(metric) + ? metric.aggType + : metric.aggregation; + + if (aggregation === 'count') { + bucketsPath[metric.name] = `${key}>_count`; + return { + ...acc, + [key]: { + filter: metric.filter + ? toElasticsearchQuery(fromKueryExpression(metric.filter)) + : { match_all: {} }, + }, + }; + } + + if (aggregation && metric.field) { + bucketsPath[metric.name] = key; + return { + ...acc, + [key]: { + [aggregation]: { field: metric.field }, + }, + }; + } + + return acc; + }, {}); + + if (isEmpty(metricAggregations)) { + return {}; + } + + return { + ...metricAggregations, + [id]: { + bucket_script: { + buckets_path: bucketsPath, + script: { + source: convertEquationToPainless(bucketsPath, equation), + lang: 'painless', + }, + }, + }, + }; +}; + +const convertEquationToPainless = (bucketsPath: { [id: string]: string }, equation?: string) => { + const workingEquation = equation || Object.keys(bucketsPath).join(' + '); + return Object.keys(bucketsPath).reduce((acc, key) => { + return acc.replace(key, `params.${key}`); + }, workingEquation); +}; diff --git a/x-pack/plugins/infra/server/lib/metrics/index.ts b/x-pack/plugins/infra/server/lib/metrics/index.ts index b234c5df357cd..0625ae4828625 100644 --- a/x-pack/plugins/infra/server/lib/metrics/index.ts +++ b/x-pack/plugins/infra/server/lib/metrics/index.ts @@ -63,42 +63,46 @@ export const query = async ( }, }; - const response = await search<{}, MetricsESResponse>(params); + try { + const response = await search<{}, MetricsESResponse>(params); - if (response.hits.total.value === 0) { - return EMPTY_RESPONSE; - } + if (response.hits.total.value === 0) { + return EMPTY_RESPONSE; + } - if (!response.aggregations) { - throw new Error('Aggregations should be present.'); - } + if (!response.aggregations) { + throw new Error('Aggregations should be present.'); + } - const { bucketSize } = calculateBucketSize({ ...options.timerange, interval }); + const { bucketSize } = calculateBucketSize({ ...options.timerange, interval }); - if (hasGroupBy) { - const aggregations = decodeOrThrow(CompositeResponseRT)(response.aggregations); - const { groupings } = aggregations; - const limit = options.limit ?? DEFAULT_LIMIT; - const returnAfterKey = !!groupings.after_key && groupings.buckets.length === limit; - const afterKey = returnAfterKey ? groupings.after_key : null; + if (hasGroupBy) { + const aggregations = decodeOrThrow(CompositeResponseRT)(response.aggregations); + const { groupings } = aggregations; + const limit = options.limit ?? DEFAULT_LIMIT; + const returnAfterKey = !!groupings.after_key && groupings.buckets.length === limit; + const afterKey = returnAfterKey ? groupings.after_key : null; + + return { + series: getSeriesFromCompositeAggregations(groupings, options, bucketSize * 1000), + info: { + afterKey, + interval: rawOptions.includeTimeseries ? bucketSize : undefined, + }, + }; + } + const aggregations = decodeOrThrow(AggregationResponseRT)(response.aggregations); return { - series: getSeriesFromCompositeAggregations(groupings, options, bucketSize * 1000), + series: getSeriesFromHistogram(aggregations, options, bucketSize * 1000), info: { - afterKey, - interval: rawOptions.includeTimeseries ? bucketSize : undefined, + afterKey: null, + interval: bucketSize, }, }; + } catch (e) { + throw e; } - - const aggregations = decodeOrThrow(AggregationResponseRT)(response.aggregations); - return { - series: getSeriesFromHistogram(aggregations, options, bucketSize * 1000), - info: { - afterKey: null, - interval: bucketSize, - }, - }; }; const getSeriesFromHistogram = ( diff --git a/x-pack/plugins/infra/server/lib/metrics/types.ts b/x-pack/plugins/infra/server/lib/metrics/types.ts index 5399d182f4185..0c87e8eca47d9 100644 --- a/x-pack/plugins/infra/server/lib/metrics/types.ts +++ b/x-pack/plugins/infra/server/lib/metrics/types.ts @@ -68,6 +68,7 @@ export const BucketRT = rt.record( MetricValueTypeRT, TermsWithMetrics, rt.record(rt.string, rt.string), + rt.type({ doc_count: rt.number }), ]) ); diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.ts index a1c9791bc20a4..299bc75f03114 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/convert_metric_to_metrics_api_metric.ts @@ -5,8 +5,10 @@ * 2.0. */ +import { isEmpty } from 'lodash'; import { networkTraffic } from '../../../../common/inventory_models/shared/metrics/snapshot/network_traffic'; import { MetricsAPIMetric, MetricsExplorerMetric } from '../../../../common/http_api'; +import { createCustomMetricsAggregations } from '../../../lib/create_custom_metrics_aggregations'; export const convertMetricToMetricsAPIMetric = ( metric: MetricsExplorerMetric, @@ -63,4 +65,18 @@ export const convertMetricToMetricsAPIMetric = ( }, }; } + + if (metric.aggregation === 'custom' && metric.custom_metrics) { + const customMetricAggregations = createCustomMetricsAggregations( + id, + metric.custom_metrics, + metric.equation + ); + if (!isEmpty(customMetricAggregations)) { + return { + id, + aggregations: customMetricAggregations, + }; + } + } }; diff --git a/x-pack/plugins/infra/server/utils/get_original_action_group.ts b/x-pack/plugins/infra/server/utils/get_original_action_group.ts new file mode 100644 index 0000000000000..8054935a03e10 --- /dev/null +++ b/x-pack/plugins/infra/server/utils/get_original_action_group.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ALERT_ACTION_GROUP } from '@kbn/rule-data-utils'; + +export const getOriginalActionGroup = ( + alertHits: Array<{ [id: string]: any }> | null | undefined +) => { + const source = alertHits && alertHits.length > 0 ? alertHits[0]._source : undefined; + return source?.[ALERT_ACTION_GROUP]; +}; diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/util.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/util.ts index 4c310b5efa5d3..1d3301bd050e6 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/util.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/util.ts @@ -107,7 +107,7 @@ export function getOperationParams( }, {}); } -function getTypeI18n(type: string) { +export function getTypeI18n(type: string) { if (type === 'number') { return i18n.translate('xpack.lens.formula.number', { defaultMessage: 'number' }); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts index 0951f950310cb..364103ed3e365 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts +++ b/x-pack/plugins/lens/public/datasources/form_based/operations/definitions/formula/validation.ts @@ -23,6 +23,7 @@ import { findMathNodes, findVariables, getOperationParams, + getTypeI18n, getValueOrName, groupArgsByType, isMathNode, @@ -121,6 +122,8 @@ export interface ErrorWrapper { severity?: 'error' | 'warning'; } +const DEFAULT_RETURN_TYPE = getTypeI18n('number'); + function getNodeLocation(node: TinymathFunction): TinymathLocation[] { return [node.location].filter(nonNullable); } @@ -131,11 +134,11 @@ function getArgumentType(arg: TinymathAST, operations: Record void) => void ): Promise { - const featureCollection = (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection; + const featureCollection = this.getFeatureCollection(); return getFeatureCollectionBounds(featureCollection, false); } async getGeoJsonWithMeta(): Promise { return { - data: (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection, + data: this.getFeatureCollection(), meta: {}, }; } @@ -130,6 +130,10 @@ export class GeoJsonFileSource extends AbstractVectorSource { areResultsTrimmed: (this._descriptor as GeojsonFileSourceDescriptor).areResultsTrimmed, }; } + + getFeatureCollection() { + return (this._descriptor as GeojsonFileSourceDescriptor).__featureCollection; + } } registerSource({ diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts index 584bdd0160b8c..dcd6a727238d5 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.test.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.test.ts @@ -33,12 +33,16 @@ import { getDataFilters, getTimeFilters, getQueryableUniqueIndexPatternIds, + getSpatialFiltersLayer, } from './map_selectors'; import { LayerDescriptor, VectorLayerDescriptor } from '../../common/descriptor_types'; +import { buildGeoShapeFilter } from '../../common/elasticsearch_util'; import { ILayer } from '../classes/layers/layer'; import { Filter } from '@kbn/es-query'; import { ESSearchSource } from '../classes/sources/es_search_source'; +import { GeoJsonFileSource } from '../classes/sources/geojson_file_source'; +import { getDefaultMapSettings } from '../reducers/map/default_map_settings'; describe('getDataFilters', () => { const mapExtent = { @@ -282,3 +286,62 @@ describe('getQueryableUniqueIndexPatternIds', () => { ).toEqual(['foo', 'fbr']); }); }); + +describe('getSpatialFiltersLayer', () => { + test('should include filters and embeddable search filters', () => { + const embeddableSearchContext = { + filters: [ + buildGeoShapeFilter({ + geometry: { + coordinates: [ + [ + [1, 0], + [1, 1], + [0, 1], + [0, 0], + [1, 0], + ], + ], + type: 'Polygon', + }, + geometryLabel: 'myShape', + geoFieldNames: ['geo.coordinates'], + }), + ], + }; + const geoJsonVectorLayer = getSpatialFiltersLayer.resultFunc( + [ + buildGeoShapeFilter({ + geometry: { + coordinates: [ + [ + [-101.21639, 48.1413], + [-101.21639, 41.84905], + [-90.95149, 41.84905], + [-90.95149, 48.1413], + [-101.21639, 48.1413], + ], + ], + type: 'Polygon', + }, + geometryLabel: 'myShape', + geoFieldNames: ['geo.coordinates'], + }), + ], + embeddableSearchContext, + getDefaultMapSettings() + ); + expect(geoJsonVectorLayer.isVisible()).toBe(true); + expect( + (geoJsonVectorLayer.getSource() as GeoJsonFileSource).getFeatureCollection().features.length + ).toBe(2); + }); + + test('should not show layer when showSpatialFilters is false', () => { + const geoJsonVectorLayer = getSpatialFiltersLayer.resultFunc([], undefined, { + ...getDefaultMapSettings(), + showSpatialFilters: false, + }); + expect(geoJsonVectorLayer.isVisible()).toBe(false); + }); +}); diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.ts b/x-pack/plugins/maps/public/selectors/map_selectors.ts index 1d46ef3015046..e8aae1c61dda1 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.ts @@ -281,11 +281,15 @@ export const getDataFilters = createSelector( export const getSpatialFiltersLayer = createSelector( getFilters, + getEmbeddableSearchContext, getMapSettings, - (filters, settings) => { + (filters, embeddableSearchContext, settings) => { const featureCollection: FeatureCollection = { type: 'FeatureCollection', - features: extractFeaturesFromFilters(filters), + features: extractFeaturesFromFilters([ + ...filters, + ...(embeddableSearchContext?.filters ?? []), + ]), }; const geoJsonSourceDescriptor = GeoJsonFileSource.createDescriptor({ __featureCollection: featureCollection, diff --git a/x-pack/plugins/ml/common/types/anomalies.ts b/x-pack/plugins/ml/common/types/anomalies.ts index 7fd71a81dfa88..1bc5ac86ff79a 100644 --- a/x-pack/plugins/ml/common/types/anomalies.ts +++ b/x-pack/plugins/ml/common/types/anomalies.ts @@ -228,6 +228,10 @@ export interface AnomalyRecordDoc { * Indicates a reduction of anomaly score if the bucket contains fewer samples than historically expected. */ incomplete_bucket_penalty?: boolean; + /** + * Indicates whether the prior distribution of the observed time series is multi-modal or has a single mode. + */ + multimodal_distribution?: boolean; }; } diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx index c0229af5683ac..fd798f53fd68b 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details_utils.tsx @@ -488,6 +488,31 @@ export const AnomalyExplanationDetails: FC<{ anomaly: AnomaliesTableRecord }> = description: explanation.high_variance_penalty ? yes : no, }); } + if (explanation.multimodal_distribution !== undefined) { + impactDetails.push({ + title: ( + + + + + + + ), + description: explanation.multimodal_distribution ? yes : no, + }); + } if (explanation.incomplete_bucket_penalty !== undefined) { impactDetails.push({ title: ( diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx index 39dbd1d168179..ac21c17b6187e 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx @@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash'; import moment from 'moment'; -import rison, { RisonValue } from '@kbn/rison'; +import rison from '@kbn/rison'; import React, { FC, useEffect, useMemo, useState } from 'react'; import { APP_ID as MAPS_APP_ID } from '@kbn/maps-plugin/common'; import { @@ -561,13 +561,15 @@ export const LinksMenuUI = (props: LinksMenuProps) => { }, }); - const appStateProps: RisonValue = { + const appStateProps = { index: dataViewId, filters: getFiltersForDSLQuery(job.datafeed_config.query, dataViewId, job.job_id), + ...(query !== null + ? { + query, + } + : {}), }; - if (query !== null) { - appStateProps.query = query; - } const _a = rison.encode(appStateProps); // Need to encode the _a parameter as it will contain characters such as '+' if using the regex. diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx index c1857979e7d53..28cf8e7c6ffd2 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx @@ -12,8 +12,12 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { euiLightVars as euiThemeLight } from '@kbn/ui-theme'; +import { createFilterManagerMock } from '@kbn/data-plugin/public/query/filter_manager/filter_manager.mock'; + import { ScatterplotMatrix } from './scatterplot_matrix'; +const mockFilterManager = createFilterManagerMock(); + const mockEsSearch = jest.fn((body) => ({ hits: { hits: [{ fields: { x: [1], y: [2] } }, { fields: { x: [2], y: [3] } }] }, })); @@ -21,6 +25,26 @@ jest.mock('../../contexts/kibana', () => ({ useMlApiContext: () => ({ esSearch: mockEsSearch, }), + useMlKibana: () => ({ + services: { + application: { + navigateToApp: jest.fn(), + }, + data: { + query: { + filterManager: mockFilterManager, + timefilter: { + timefilter: { + getTime: jest.fn(() => { + return { from: '', to: '' }; + }), + getRefreshInterval: jest.fn(), + }, + }, + }, + }, + }, + }), })); const mockEuiTheme = euiThemeLight; diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx index e822d2ebd91d7..6e718e0f0ccd8 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import React, { useMemo, useEffect, useState, FC } from 'react'; - +import React, { useMemo, useEffect, useState, FC, useCallback } from 'react'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import rison from '@kbn/rison'; import { EuiCallOut, @@ -17,12 +17,14 @@ import { EuiFlexItem, EuiFormRow, EuiIconTip, + EuiLink, EuiSelect, EuiSpacer, EuiSwitch, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { Query } from '@kbn/data-plugin/common/query'; import { DataView } from '@kbn/data-views-plugin/public'; import { stringHash } from '@kbn/ml-string-hash'; @@ -31,7 +33,7 @@ import { isRuntimeMappings } from '../../../../common/util/runtime_field_utils'; import { RuntimeMappings } from '../../../../common/types/fields'; import { getCombinedRuntimeMappings } from '../data_grid'; -import { useMlApiContext } from '../../contexts/kibana'; +import { useMlApiContext, useMlKibana } from '../../contexts/kibana'; import { getProcessedFields } from '../data_grid'; import { useCurrentEuiTheme } from '../color_range_legend'; @@ -101,6 +103,7 @@ export interface ScatterplotMatrixProps { searchQuery?: estypes.QueryDslQueryContainer; runtimeMappings?: RuntimeMappings; indexPattern?: DataView; + query?: Query; } export const ScatterplotMatrix: FC = ({ @@ -112,9 +115,13 @@ export const ScatterplotMatrix: FC = ({ searchQuery, runtimeMappings, indexPattern, + query, }) => { const { esSearch } = useMlApiContext(); - + const kibana = useMlKibana(); + const { + services: { application, data }, + } = kibana; // dynamicSize is optionally used for outlier charts where the scatterplot marks // are sized according to outlier_score const [dynamicSize, setDynamicSize] = useState(false); @@ -142,6 +149,8 @@ export const ScatterplotMatrix: FC = ({ { items: any[]; backgroundItems: any[]; columns: string[]; messages: string[] } | undefined >(); + const { euiTheme } = useCurrentEuiTheme(); + // formats the array of field names for EuiComboBox const fieldOptions = useMemo( () => @@ -172,7 +181,77 @@ export const ScatterplotMatrix: FC = ({ setDynamicSize(!dynamicSize); }; - const { euiTheme } = useCurrentEuiTheme(); + const getCustomVisualizationLink = useCallback(() => { + const { columns } = splom!; + const outlierScoreField = + resultsField !== undefined ? `${resultsField}.${OUTLIER_SCORE_FIELD}` : undefined; + const vegaSpec = getScatterplotMatrixVegaLiteSpec( + true, + [], + [], + columns, + euiTheme, + resultsField, + color, + legendType, + dynamicSize + ); + + vegaSpec.$schema = 'https://vega.github.io/schema/vega-lite/v5.json'; + vegaSpec.title = `Scatterplot matrix for ${index}`; + + const fieldsToFetch = [ + ...columns, + // Add outlier_score field in fetch if it's available so custom visualization can use it + ...(outlierScoreField ? [outlierScoreField] : []), + // Add field to color code by in fetch so custom visualization can use it - usually for classfication jobs + ...(color ? [color] : []), + ]; + + vegaSpec.data = { + url: { + '%context%': true, + ...(indexPattern?.timeFieldName + ? { ['%timefield%']: `${indexPattern?.timeFieldName}` } + : {}), + index, + body: { + fields: fieldsToFetch, + size: fetchSize, + _source: false, + }, + }, + format: { property: 'hits.hits' }, + }; + + const globalState = encodeURIComponent( + rison.encode({ + filters: data.query.filterManager.getFilters(), + refreshInterval: data.query.timefilter.timefilter.getRefreshInterval(), + time: data.query.timefilter.timefilter.getTime(), + }) + ); + + const appState = encodeURIComponent( + rison.encode({ + filters: [], + linked: false, + query, + uiState: {}, + vis: { + aggs: [], + params: { + spec: JSON.stringify(vegaSpec, null, 2), + }, + }, + }) + ); + + const basePath = `/create?type=vega&_g=${globalState}&_a=${appState}`; + + return { path: basePath }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [splom]); useEffect(() => { if (fields.length === 0) { @@ -316,6 +395,7 @@ export const ScatterplotMatrix: FC = ({ const { items, backgroundItems, columns } = splom; return getScatterplotMatrixVegaLiteSpec( + false, items, backgroundItems, columns, @@ -442,6 +522,29 @@ export const ScatterplotMatrix: FC = ({ )} + {splom ? ( + + { + const customVisLink = getCustomVisualizationLink(); + await application.navigateToApp('visualize#', { + path: customVisLink.path, + openInNewTab: false, + }); + }} + data-test-subj="mlSplomoExploreInCustomVisualizationLink" + > + + + + ) : null}
{splom.messages.length > 0 && ( diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts index 0fbe08dd24af7..dfa2389c6e0a5 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.test.ts @@ -26,7 +26,7 @@ import { describe('getColorSpec()', () => { it('should return only user selection conditions and the default color for non-outlier specs', () => { - const colorSpec = getColorSpec(euiThemeLight); + const colorSpec = getColorSpec(false, euiThemeLight); expect(colorSpec).toEqual({ condition: [{ selection: USER_SELECTION }, { selection: SINGLE_POINT_CLICK }], @@ -35,7 +35,7 @@ describe('getColorSpec()', () => { }); it('should return user selection condition and conditional spec for outliers', () => { - const colorSpec = getColorSpec(euiThemeLight, 'outlier_score'); + const colorSpec = getColorSpec(false, euiThemeLight, 'outlier_score'); expect(colorSpec).toEqual({ condition: { @@ -53,7 +53,13 @@ describe('getColorSpec()', () => { it('should return user selection condition and a field based spec for non-outlier specs with legendType supplied', () => { const colorName = 'the-color-field'; - const colorSpec = getColorSpec(euiThemeLight, undefined, colorName, LEGEND_TYPES.NOMINAL); + const colorSpec = getColorSpec( + false, + euiThemeLight, + undefined, + colorName, + LEGEND_TYPES.NOMINAL + ); expect(colorSpec).toEqual({ condition: { @@ -70,10 +76,18 @@ describe('getColorSpec()', () => { }); describe('getScatterplotMatrixVegaLiteSpec()', () => { + const forCustomLink = false; + it('should return the default spec for non-outliers without a legend', () => { const data = [{ x: 1, y: 1 }]; - const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec(data, [], ['x', 'y'], euiThemeLight); + const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( + forCustomLink, + data, + [], + ['x', 'y'], + euiThemeLight + ); const specForegroundLayer = vegaLiteSpec.spec.layer[0]; // A valid Vega Lite spec shouldn't throw an error when compiled. @@ -103,6 +117,7 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { const data = [{ x: 1, y: 1 }]; const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( + forCustomLink, data, [], ['x', 'y'], @@ -151,6 +166,7 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { const data = [{ x: 1, y: 1 }]; const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( + forCustomLink, data, [], ['x', 'y'], @@ -196,6 +212,7 @@ describe('getScatterplotMatrixVegaLiteSpec()', () => { const data = [{ ['x.a']: 1, ['y[a]']: 1 }]; const vegaLiteSpec = getScatterplotMatrixVegaLiteSpec( + forCustomLink, data, [], ['x.a', 'y[a]'], diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts index de29332f57ef6..31ec6403b8fd0 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix_vega_lite_spec.ts @@ -29,8 +29,10 @@ export const COLOR_SELECTION = euiPaletteColorBlind()[2]; export const COLOR_RANGE_OUTLIER = [euiPaletteColorBlind()[1], euiPaletteColorBlind()[2]]; export const COLOR_RANGE_NOMINAL = euiPaletteColorBlind({ rotations: 2 }); export const COLOR_RANGE_QUANTITATIVE = euiPalettePositive(5); +const CUSTOM_VIS_FIELDS_PATH = 'fields'; export const getColorSpec = ( + forCustomVisLink: boolean, euiTheme: typeof euiThemeLight, escapedOutlierScoreField?: string, color?: string, @@ -58,7 +60,10 @@ export const getColorSpec = ( return { condition: { selection: USER_SELECTION, - field: getEscapedVegaFieldName(color ?? '00FF00'), + field: `${forCustomVisLink ? `${CUSTOM_VIS_FIELDS_PATH}.` : ''}${getEscapedVegaFieldName( + color ?? '00FF00' + // When creating the custom link - this field is returned in an array so we need to access it + )}${forCustomVisLink ? '[0]' : ''}`, type: legendType, scale: { range: @@ -76,6 +81,7 @@ export const getColorSpec = ( }; const getVegaSpecLayer = ( + forCustomVisLink: boolean, isBackground: boolean, values: VegaValue[], colorSpec: any, @@ -117,7 +123,8 @@ const getVegaSpecLayer = ( }; return { - data: { values: [...values] }, + // Don't need to add static data for custom vis links + ...(forCustomVisLink ? {} : { data: { values: [...values] } }), mark: { ...(outliers && dynamicSize ? { @@ -133,7 +140,9 @@ const getVegaSpecLayer = ( ? { transform: [ { - calculate: `datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff`, + calculate: `datum${ + forCustomVisLink ? `.${CUSTOM_VIS_FIELDS_PATH}` : '' + }['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff`, as: 'is_outlier', }, ], @@ -154,7 +163,9 @@ const getVegaSpecLayer = ( opacity: { condition: { value: 1, - test: `(datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, + test: `(datum${ + forCustomVisLink ? `.${CUSTOM_VIS_FIELDS_PATH}` : '' + }['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, }, value: 0.5, }, @@ -162,19 +173,27 @@ const getVegaSpecLayer = ( : {}), ...(outliers ? { - order: { field: escapedOutlierScoreField }, + order: { + field: `${ + forCustomVisLink ? `${CUSTOM_VIS_FIELDS_PATH}.` : '' + }${escapedOutlierScoreField}`, + }, size: { ...(!dynamicSize ? { condition: { value: 40, - test: `(datum['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, + test: `(datum${ + forCustomVisLink ? `.${CUSTOM_VIS_FIELDS_PATH}` : '' + }['${escapedOutlierScoreField}'] >= mlOutlierScoreThreshold.cutoff)`, }, value: 8, } : { type: LEGEND_TYPES.QUANTITATIVE, - field: escapedOutlierScoreField, + field: `${ + forCustomVisLink ? `${CUSTOM_VIS_FIELDS_PATH}.` : '' + }${escapedOutlierScoreField}`, scale: { type: 'linear', range: [8, 200], @@ -197,7 +216,14 @@ const getVegaSpecLayer = ( tooltip: [ ...(color !== undefined ? // @ts-ignore - [{ type: colorSpec.condition.type, field: getEscapedVegaFieldName(color) }] + [ + { + type: colorSpec.condition.type, + field: `${ + forCustomVisLink ? `${CUSTOM_VIS_FIELDS_PATH}.` : '' + }${getEscapedVegaFieldName(color)}`, + }, + ] : []), ...vegaColumns.map((d) => ({ type: LEGEND_TYPES.QUANTITATIVE, @@ -207,7 +233,9 @@ const getVegaSpecLayer = ( ? [ { type: LEGEND_TYPES.QUANTITATIVE, - field: escapedOutlierScoreField, + field: `${ + forCustomVisLink ? `${CUSTOM_VIS_FIELDS_PATH}.` : '' + }${escapedOutlierScoreField}`, format: '.3f', }, ] @@ -215,21 +243,22 @@ const getVegaSpecLayer = ( ], }, ...(isBackground ? {} : selection), - width: SCATTERPLOT_SIZE, - height: SCATTERPLOT_SIZE, + ...(forCustomVisLink ? {} : { width: SCATTERPLOT_SIZE }), + ...(forCustomVisLink ? {} : { height: SCATTERPLOT_SIZE }), }; }; // Escapes the characters .[] in field names with double backslashes // since VEGA treats dots/brackets in field names as nested values. // See https://vega.github.io/vega-lite/docs/field.html for details. -function getEscapedVegaFieldName(fieldName: string) { - return fieldName.replace(/([\.|\[|\]])/g, '\\$1'); +function getEscapedVegaFieldName(fieldName: string, prependString: string = '') { + return `${prependString}${fieldName.replace(/([\.|\[|\]])/g, '\\$1')}`; } type VegaValue = Record; export const getScatterplotMatrixVegaLiteSpec = ( + forCustomVisLink: boolean, values: VegaValue[], backgroundValues: VegaValue[], columns: string[], @@ -240,12 +269,15 @@ export const getScatterplotMatrixVegaLiteSpec = ( dynamicSize?: boolean ): TopLevelSpec => { const vegaValues = values; - const vegaColumns = columns.map(getEscapedVegaFieldName); + const vegaColumns = columns.map((column) => + getEscapedVegaFieldName(column, forCustomVisLink ? 'fields.' : '') + ); const outliers = resultsField !== undefined; const escapedOutlierScoreField = `${resultsField}\\.${OUTLIER_SCORE_FIELD}`; const colorSpec = getColorSpec( + forCustomVisLink, euiTheme, resultsField && escapedOutlierScoreField, color, @@ -282,6 +314,7 @@ export const getScatterplotMatrixVegaLiteSpec = ( spec: { layer: [ getVegaSpecLayer( + forCustomVisLink, false, vegaValues, colorSpec, @@ -298,6 +331,7 @@ export const getScatterplotMatrixVegaLiteSpec = ( if (backgroundValues.length) { schema.spec.layer.unshift( getVegaSpecLayer( + forCustomVisLink, true, backgroundValues, colorSpec, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx index c10c3e67be443..6cb860557b719 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx @@ -225,6 +225,7 @@ export const ExplorationPageWrapper: FC = ({ = React.memo(({ jobId }) = index={jobConfig?.dest.index} resultsField={jobConfig?.dest.results_field} searchQuery={searchQuery} + query={query} /> )} {showLegacyFeatureInfluenceFormatCallout && ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx index fcd413b301108..4b482e2de1ed8 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx @@ -55,6 +55,7 @@ const getMockedTimefilter = () => { enableAutoRefreshSelector: jest.fn(), getRefreshInterval: jest.fn(), setRefreshInterval: jest.fn(), + getActiveBounds: jest.fn(), getTime: jest.fn(), isAutoRefreshSelectorEnabled: jest.fn(), isTimeRangeSelectorEnabled: jest.fn(), @@ -68,7 +69,7 @@ const getMockedTimefilter = () => { }; }; -const getMockedDatePickeDependencies = () => { +const getMockedDatePickerDependencies = () => { return { data: { query: { @@ -138,7 +139,7 @@ describe('TimeSeriesExplorerUrlStateManager', () => { render( - + diff --git a/x-pack/plugins/ml/public/ui_actions/apply_entity_filters_action.tsx b/x-pack/plugins/ml/public/ui_actions/apply_entity_filters_action.tsx index 151bee3c8ac90..ec823fd13ea46 100644 --- a/x-pack/plugins/ml/public/ui_actions/apply_entity_filters_action.tsx +++ b/x-pack/plugins/ml/public/ui_actions/apply_entity_filters_action.tsx @@ -78,7 +78,11 @@ export function createApplyEntityFieldFiltersAction( const filter = filterManager .getFilters() .find( - (f) => f.meta.key === field.fieldName && f.meta.params.query === field.fieldValue + (f) => + f.meta.key === field.fieldName && + typeof f.meta.params === 'object' && + 'query' in f.meta.params && + f.meta.params.query === field.fieldValue ); if (filter) { filterManager.removeFilter(filter); diff --git a/x-pack/plugins/observability/public/data/slo/slo.ts b/x-pack/plugins/observability/public/data/slo/slo.ts index 6d22027b908fd..047083c28986c 100644 --- a/x-pack/plugins/observability/public/data/slo/slo.ts +++ b/x-pack/plugins/observability/public/data/slo/slo.ts @@ -92,7 +92,7 @@ export const sloList: FindSLOResponse = { }, { ...baseSlo, - id: 'c0f8d669-9177-4706-9098-f397a88173a6', + id: 'c0f8d669-9277-4706-9098-f397a88173a6', summary: buildViolatedSummary(), timeWindow: buildRollingTimeWindow({ duration: '7d' }), }, diff --git a/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts b/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts index 31f8202c3c77e..1b810117fa288 100644 --- a/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts +++ b/x-pack/plugins/observability/public/hooks/slo/use_create_slo.ts @@ -41,6 +41,9 @@ export function useCreateOrUpdateSlo(): UseCreateOrUpdateSlo { setSuccess(true); } catch (e) { setError(e); + } finally { + setSuccess(false); + setLoading(false); } }, [http] @@ -58,6 +61,9 @@ export function useCreateOrUpdateSlo(): UseCreateOrUpdateSlo { setSuccess(true); } catch (e) { setError(e); + } finally { + setSuccess(false); + setLoading(false); } }, [http] diff --git a/x-pack/plugins/observability/public/hooks/use_create_data_view.ts b/x-pack/plugins/observability/public/hooks/use_create_data_view.ts new file mode 100644 index 0000000000000..e2257ef71ab06 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_create_data_view.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useState } from 'react'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { useKibana } from '../utils/kibana_react'; + +interface UseCreateDataViewProps { + indexPatternString: string | undefined; +} + +export function useCreateDataView({ indexPatternString }: UseCreateDataViewProps) { + const { dataViews } = useKibana().services; + + const [stateDataView, setStateDataView] = useState(); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + const createDataView = () => + dataViews.create({ + title: indexPatternString, + allowNoIndex: true, + }); + + if (indexPatternString) { + setIsLoading(true); + createDataView() + .then((value) => { + setStateDataView(value); + }) + .finally(() => { + setIsLoading(false); + }); + } + }, [indexPatternString, dataViews]); + + return { dataView: stateDataView, loading: isLoading }; +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx new file mode 100644 index 0000000000000..d2e52fa27bd90 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ComponentStory } from '@storybook/react'; + +import { FormProvider, useForm } from 'react-hook-form'; +import { KibanaReactStorybookDecorator } from '../../../../utils/kibana_react.storybook_decorator'; +import { QueryBuilder as Component, Props } from './query_builder'; +import { SLO_EDIT_FORM_DEFAULT_VALUES } from '../../constants'; + +export default { + component: Component, + title: 'app/SLO/EditPage/CustomKQL/QueryBuilder', + decorators: [KibanaReactStorybookDecorator], +}; + +const Template: ComponentStory = (props: Props) => { + const methods = useForm({ defaultValues: SLO_EDIT_FORM_DEFAULT_VALUES }); + return ( + + + + ); +}; + +const defaultProps = { + dataTestSubj: 'dataTestSubj', + indexPatternString: 'log*', + name: 'name' as const, + placeholder: 'Enter something if you dare', +}; + +export const QueryBuilder = Template.bind({}); +QueryBuilder.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx new file mode 100644 index 0000000000000..bf21c120cd01d --- /dev/null +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/custom_kql/query_builder.tsx @@ -0,0 +1,74 @@ +/* + * 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 { Control, Controller, FieldPath } from 'react-hook-form'; +import { EuiFormRow } from '@elastic/eui'; +import { CreateSLOInput } from '@kbn/slo-schema'; +import { QueryStringInput } from '@kbn/unified-search-plugin/public'; +import { useKibana } from '../../../../utils/kibana_react'; +import { useCreateDataView } from '../../../../hooks/use_create_data_view'; + +export interface Props { + control: Control; + dataTestSubj: string; + indexPatternString: string | undefined; + label: string; + name: FieldPath; + placeholder: string; +} + +export function QueryBuilder({ + control, + dataTestSubj, + indexPatternString, + label, + name, + placeholder, +}: Props) { + const { data, dataViews, docLinks, http, notifications, storage, uiSettings, unifiedSearch } = + useKibana().services; + + const { dataView } = useCreateDataView({ indexPatternString }); + + return ( + + ( + { + field.onChange(value.query); + }} + /> + )} + /> + + ); +} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx index 1c0191057aa11..455e36d2e818e 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form.tsx @@ -144,7 +144,7 @@ export function SloEditForm({ slo }: Props) { {watch('indicator.type') === 'sli.kql.custom' ? ( - + ) : null} diff --git a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_definition_custom_kql.tsx b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_definition_custom_kql.tsx index 257185a787728..29d3386f30ad4 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_definition_custom_kql.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/components/slo_edit_form_definition_custom_kql.tsx @@ -6,18 +6,20 @@ */ import React from 'react'; -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiFormLabel, EuiSuggest } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Control, Controller } from 'react-hook-form'; +import { Control, UseFormWatch } from 'react-hook-form'; import type { CreateSLOInput } from '@kbn/slo-schema'; import { IndexSelection } from './custom_kql/index_selection'; +import { QueryBuilder } from './custom_kql/query_builder'; export interface Props { control: Control; + watch: UseFormWatch; } -export function SloEditFormDefinitionCustomKql({ control }: Props) { +export function SloEditFormDefinitionCustomKql({ control, watch }: Props) { return ( @@ -25,88 +27,64 @@ export function SloEditFormDefinitionCustomKql({ control }: Props) { - - {i18n.translate('xpack.observability.slos.sloEdit.sloDefinition.customKql.queryFilter', { - defaultMessage: 'Query filter', - })} - - ( - KQL} - status="unchanged" - aria-label="Filter query" - data-test-subj="sloFormCustomKqlFilterQueryInput" - placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.sloDefinition.customKql.customFilter', - { - defaultMessage: 'Custom filter to apply on the index', - } - )} - suggestions={[]} - {...field} - /> + dataTestSubj="sloFormCustomKqlFilterQueryInput" + indexPatternString={watch('indicator.params.index')} + label={i18n.translate( + 'xpack.observability.slos.sloEdit.sloDefinition.customKql.queryFilter', + { + defaultMessage: 'Query filter', + } + )} + name="indicator.params.filter" + placeholder={i18n.translate( + 'xpack.observability.slos.sloEdit.sloDefinition.customKql.customFilter', + { + defaultMessage: 'Custom filter to apply on the index', + } )} /> - - {i18n.translate('xpack.observability.slos.sloEdit.sloDefinition.customKql.goodQuery', { - defaultMessage: 'Good query', - })} - - ( - KQL} - status="unchanged" - aria-label="Good filter" - data-test-subj="sloFormCustomKqlGoodQueryInput" - placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.sloDefinition.customKql.goodQueryPlaceholder', - { - defaultMessage: 'Define the good events', - } - )} - suggestions={[]} - {...field} - /> + dataTestSubj="sloFormCustomKqlGoodQueryInput" + indexPatternString={watch('indicator.params.index')} + label={i18n.translate( + 'xpack.observability.slos.sloEdit.sloDefinition.customKql.goodQuery', + { + defaultMessage: 'Good query', + } + )} + name="indicator.params.good" + placeholder={i18n.translate( + 'xpack.observability.slos.sloEdit.sloDefinition.customKql.goodQueryPlaceholder', + { + defaultMessage: 'Define the good events', + } )} /> - - {i18n.translate('xpack.observability.slos.sloEdit.sloDefinition.customKql.totalQuery', { - defaultMessage: 'Total query', - })} - - ( - KQL} - status="unchanged" - aria-label="Total filter" - data-test-subj="sloFormCustomKqlTotalQueryInput" - placeholder={i18n.translate( - 'xpack.observability.slos.sloEdit.sloDefinition.customKql.totalQueryPlaceholder', - { - defaultMessage: 'Define the total events', - } - )} - suggestions={[]} - {...field} - /> + dataTestSubj="sloFormCustomKqlTotalQueryInput" + indexPatternString={watch('indicator.params.index')} + label={i18n.translate( + 'xpack.observability.slos.sloEdit.sloDefinition.customKql.totalQuery', + { + defaultMessage: 'Total query', + } + )} + name="indicator.params.total" + placeholder={i18n.translate( + 'xpack.observability.slos.sloEdit.sloDefinition.customKql.totalQueryPlaceholder', + { + defaultMessage: 'Define the total events', + } )} /> diff --git a/x-pack/plugins/observability/public/pages/slo_edit/index.test.tsx b/x-pack/plugins/observability/public/pages/slo_edit/index.test.tsx index 64e1568904896..e9baaf105e10a 100644 --- a/x-pack/plugins/observability/public/pages/slo_edit/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/slo_edit/index.test.tsx @@ -56,7 +56,23 @@ const mockBasePathPrepend = jest.fn(); const mockKibana = () => { useKibanaMock.mockReturnValue({ services: { - application: { navigateToUrl: mockNavigate }, + application: { + navigateToUrl: mockNavigate, + }, + data: { + dataViews: { + find: jest.fn().mockReturnValue([]), + get: jest.fn().mockReturnValue([]), + }, + }, + dataViews: { + create: jest.fn().mockResolvedValue(42), + }, + docLinks: { + links: { + query: {}, + }, + }, http: { basePath: { prepend: mockBasePathPrepend, @@ -64,8 +80,19 @@ const mockKibana = () => { }, notifications: { toasts: { - addSuccess: mockAddSuccess, addError: mockAddError, + addSuccess: mockAddSuccess, + }, + }, + storage: { + get: () => {}, + }, + uiSettings: { + get: () => {}, + }, + unifiedSearch: { + autocomplete: { + hasQuerySuggestions: () => {}, }, }, }, diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx index e91d42fb5b438..ac7785cd4921c 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list.tsx @@ -24,11 +24,11 @@ export function SloList() { const [sort, setSort] = useState('name'); const [indicatorTypeFilter, setIndicatorTypeFilter] = useState([]); - const [deleting, setIsDeleting] = useState(false); + const [isCloningOrDeleting, setIsCloningOrDeleting] = useState(false); const [shouldReload, setShouldReload] = useState(false); const { - loading, + loading: isLoadingSloList, error, sloList: { results: sloList = [], total, perPage }, } = useFetchSloList({ @@ -42,16 +42,19 @@ export function SloList() { useEffect(() => { if (shouldReload) { setShouldReload(false); - setIsDeleting(false); } - }, [shouldReload]); - const handleDeleted = () => { - setShouldReload(true); + if (!isLoadingSloList) { + setIsCloningOrDeleting(false); + } + }, [isLoadingSloList, shouldReload]); + + const handleCloningOrDeleting = () => { + setIsCloningOrDeleting(true); }; - const handleDeleting = () => { - setIsDeleting(true); + const handleClonedOrDeleted = () => { + setShouldReload(true); }; const handlePageClick = (pageNumber: number) => { @@ -79,7 +82,7 @@ export function SloList() { diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.stories.tsx index 17d7c74ccc066..9eaec700ba3cf 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.stories.tsx @@ -29,6 +29,10 @@ const Template: ComponentStory = (props: SloListItemProps) => const defaultProps = { slo: buildSlo(), historicalSummary: historicalSummaryData[HEALTHY_ROLLING_SLO], + onCloned: () => {}, + onCloning: () => {}, + onDeleted: () => {}, + onDeleting: () => {}, }; export const SloListItem = Template.bind({}); diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx index 0ea2f14a97352..03d5b5326e3a6 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_item.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiButtonIcon, EuiContextMenuItem, @@ -20,15 +20,22 @@ import { i18n } from '@kbn/i18n'; import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useKibana } from '../../../utils/kibana_react'; +import { useCreateOrUpdateSlo } from '../../../hooks/slo/use_create_slo'; import { SloSummary } from './slo_summary'; import { SloDeleteConfirmationModal } from './slo_delete_confirmation_modal'; import { SloBadges } from './badges/slo_badges'; +import { + transformSloResponseToCreateSloInput, + transformValuesToCreateSLOInput, +} from '../../slo_edit/helpers/process_slo_form_values'; import { paths } from '../../../config'; export interface SloListItemProps { slo: SLOWithSummaryResponse; historicalSummary?: HistoricalSummaryResponse[]; historicalSummaryLoading: boolean; + onCloned: () => void; + onCloning: () => void; onDeleted: () => void; onDeleting: () => void; } @@ -37,6 +44,8 @@ export function SloListItem({ slo, historicalSummary = [], historicalSummaryLoading, + onCloned, + onCloning, onDeleted, onDeleting, }: SloListItemProps) { @@ -45,6 +54,8 @@ export function SloListItem({ http: { basePath }, } = useKibana().services; + const { createSlo, loading: isCloning, success: isCloned } = useCreateOrUpdateSlo(); + const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); @@ -57,6 +68,15 @@ export function SloListItem({ navigateToUrl(basePath.prepend(paths.observability.sloEdit(slo.id))); }; + const handleClone = () => { + const newSlo = transformValuesToCreateSLOInput( + transformSloResponseToCreateSloInput({ ...slo, name: `[Copy] ${slo.name}` })! + ); + + createSlo(newSlo); + setIsActionsPopoverOpen(false); + }; + const handleDelete = () => { setDeleteConfirmationModalOpen(true); setIsDeleting(true); @@ -73,12 +93,23 @@ export function SloListItem({ onDeleted(); }; + useEffect(() => { + if (isCloning) { + onCloning(); + } + + if (isCloned) { + onCloned(); + } + }, [isCloned, isCloning, onCloned, onCloning]); + return ( {/* CONTENT */} @@ -124,12 +155,32 @@ export function SloListItem({ + {i18n.translate('xpack.observability.slos.slo.item.actions.edit', { defaultMessage: 'Edit', })} , - + + {i18n.translate('xpack.observability.slos.slo.item.actions.clone', { + defaultMessage: 'Clone', + })} + , + {i18n.translate('xpack.observability.slos.slo.item.actions.delete', { defaultMessage: 'Delete', })} diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx index 560353e483960..d51a454d002b9 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.stories.tsx @@ -24,6 +24,8 @@ const defaultProps: Props = { sloList: sloList.results, loading: false, error: false, + onCloned: () => {}, + onCloning: () => {}, onDeleted: () => {}, onDeleting: () => {}, }; diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx index 71ba38092e6f7..c911d8ab19774 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_list_items.tsx @@ -17,11 +17,21 @@ export interface Props { sloList: SLOWithSummaryResponse[]; loading: boolean; error: boolean; + onCloned: () => void; + onCloning: () => void; onDeleted: () => void; onDeleting: () => void; } -export function SloListItems({ sloList, loading, error, onDeleted, onDeleting }: Props) { +export function SloListItems({ + sloList, + loading, + error, + onCloned, + onCloning, + onDeleted, + onDeleting, +}: Props) { const [sloIds, setSloIds] = useState([]); useEffect(() => { setSloIds(sloList.map((slo) => slo.id)); @@ -45,6 +55,8 @@ export function SloListItems({ sloList, loading, error, onDeleted, onDeleting }: slo={slo} historicalSummary={historicalSummaryBySlo[slo.id]} historicalSummaryLoading={historicalSummaryLoading} + onCloned={onCloned} + onCloning={onCloning} onDeleted={onDeleted} onDeleting={onDeleting} /> diff --git a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx index e9d90c76ae6a8..029e18e4f38db 100644 --- a/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx +++ b/x-pack/plugins/observability/public/pages/slos/components/slo_summary.tsx @@ -64,9 +64,9 @@ export function SloSummary({ slo, historicalSummary = [], historicalSummaryLoadi - + - + ({ @@ -29,14 +33,26 @@ jest.mock('../../utils/kibana_react'); jest.mock('../../hooks/use_breadcrumbs'); jest.mock('../../hooks/use_license'); jest.mock('../../hooks/slo/use_fetch_slo_list'); +jest.mock('../../hooks/slo/use_create_slo'); +jest.mock('../../hooks/slo/use_delete_slo'); jest.mock('../../hooks/slo/use_fetch_historical_summary'); const useKibanaMock = useKibana as jest.Mock; const useLicenseMock = useLicense as jest.Mock; const useFetchSloListMock = useFetchSloList as jest.Mock; +const useCreateOrUpdateSloMock = useCreateOrUpdateSlo as jest.Mock; +const useDeleteSloMock = useDeleteSlo as jest.Mock; const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock; +const mockCreateSlo = jest.fn(); +useCreateOrUpdateSloMock.mockReturnValue({ createSlo: mockCreateSlo }); + +const mockDeleteSlo = jest.fn(); +useDeleteSloMock.mockReturnValue({ deleteSlo: mockDeleteSlo }); + const mockNavigate = jest.fn(); +const mockAddSuccess = jest.fn(); +const mockAddError = jest.fn(); const mockKibana = () => { useKibanaMock.mockReturnValue({ @@ -48,6 +64,12 @@ const mockKibana = () => { prepend: jest.fn(), }, }, + notifications: { + toasts: { + addSuccess: mockAddSuccess, + addError: mockAddError, + }, + }, }, }); }; @@ -90,9 +112,12 @@ describe('SLOs Page', () => { }); describe('when the correct license is found', () => { + beforeEach(() => { + useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + }); + it('renders nothing when the API is loading', async () => { useFetchSloListMock.mockReturnValue({ loading: true, sloList: emptySloList }); - useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); const { container } = render(, config); @@ -101,16 +126,15 @@ describe('SLOs Page', () => { it('renders the SLOs Welcome Prompt when the API has finished loading and there are no results', async () => { useFetchSloListMock.mockReturnValue({ loading: false, sloList: emptySloList }); - useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); render(, config); expect(screen.queryByTestId('slosPageWelcomePrompt')).toBeTruthy(); }); - it('renders the SLOs page when the API has finished loading and there are results', async () => { + it('should have a create new SLO button', () => { useFetchSloListMock.mockReturnValue({ loading: false, sloList }); - useLicenseMock.mockReturnValue({ hasAtLeast: () => true }); + useFetchHistoricalSummaryMock.mockReturnValue({ loading: false, data: historicalSummaryData, @@ -118,8 +142,96 @@ describe('SLOs Page', () => { render(, config); - expect(screen.queryByTestId('slosPage')).toBeTruthy(); - expect(screen.queryByTestId('sloList')).toBeTruthy(); + expect(screen.getByText('Create new SLO')).toBeTruthy(); + }); + + describe('when API has returned results', () => { + it('renders the SLO list with SLO items', async () => { + useFetchSloListMock.mockReturnValue({ loading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + loading: false, + data: historicalSummaryData, + }); + + render(, config); + + expect(screen.queryByTestId('slosPage')).toBeTruthy(); + expect(screen.queryByTestId('sloList')).toBeTruthy(); + expect(screen.queryAllByTestId('sloItem')).toBeTruthy(); + expect(screen.queryAllByTestId('sloItem').length).toBe(sloList.results.length); + }); + + it('allows editing an SLO', async () => { + useFetchSloListMock.mockReturnValue({ loading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + loading: false, + data: historicalSummaryData, + }); + + render(, config); + + screen.getAllByLabelText('Actions').at(0)?.click(); + + await waitForEuiPopoverOpen(); + + const button = screen.getByTestId('sloActionsEdit'); + + expect(button).toBeTruthy(); + + button.click(); + + expect(mockNavigate).toBeCalled(); + }); + + it('allows deleting an SLO', async () => { + useFetchSloListMock.mockReturnValue({ loading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + loading: false, + data: historicalSummaryData, + }); + + render(, config); + + screen.getAllByLabelText('Actions').at(0)?.click(); + + await waitForEuiPopoverOpen(); + + const button = screen.getByTestId('sloActionsDelete'); + + expect(button).toBeTruthy(); + + button.click(); + + screen.getByTestId('confirmModalConfirmButton').click(); + + expect(mockDeleteSlo).toBeCalledWith(sloList.results.at(0)?.id); + }); + + it('allows cloning an SLO', async () => { + useFetchSloListMock.mockReturnValue({ loading: false, sloList }); + + useFetchHistoricalSummaryMock.mockReturnValue({ + loading: false, + data: historicalSummaryData, + }); + + render(, config); + + screen.getAllByLabelText('Actions').at(0)?.click(); + + await waitForEuiPopoverOpen(); + + const button = screen.getByTestId('sloActionsClone'); + + expect(button).toBeTruthy(); + + button.click(); + + expect(mockCreateSlo).toBeCalled(); + }); }); }); }); diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts index 8838457b3a344..17a21f41301b6 100644 --- a/x-pack/plugins/observability/public/plugin.ts +++ b/x-pack/plugins/observability/public/plugin.ts @@ -42,6 +42,7 @@ import { SecurityPluginStart } from '@kbn/security-plugin/public'; import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import { LicensingPluginStart } from '@kbn/licensing-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import { RuleDetailsLocatorDefinition } from './locators/rule_details'; import { observabilityAppId, observabilityFeatureId, casesPath } from '../common'; import { createLazyObservabilityPageTemplate } from './components/shared'; @@ -107,6 +108,7 @@ export interface ObservabilityPublicPluginsStart { spaces: SpacesPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; usageCollection: UsageCollectionSetup; + unifiedSearch: UnifiedSearchPublicPluginStart; home?: HomePublicPluginStart; } diff --git a/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx b/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx index a0f6c1f7a3192..bf84059db3b6a 100644 --- a/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx +++ b/x-pack/plugins/observability/public/utils/kibana_react.storybook_decorator.tsx @@ -11,17 +11,35 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) { return ( {}, + }, charts: { theme: { - useChartsTheme: () => {}, useChartsBaseTheme: () => {}, + useChartsTheme: () => {}, + }, + }, + data: {}, + dataViews: { + create: () => {}, + }, + docLinks: { + links: { + query: {}, + }, + }, + http: { + basePath: { + prepend: (_: string) => '', }, }, - application: { navigateToUrl: () => {} }, - http: { basePath: { prepend: (_: string) => '' } }, - docLinks: { links: { query: {} } }, - notifications: { toasts: {} }, - storage: { get: () => {} }, + notifications: { + toasts: {}, + }, + storage: { + get: () => {}, + }, uiSettings: { get: (setting: string) => { if (setting === 'dateFormat') { @@ -29,6 +47,7 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) { } }, }, + unifiedSearch: {}, }} > diff --git a/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts b/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts index ec8aec44e78ec..b10d4abb0e33c 100644 --- a/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts +++ b/x-pack/plugins/observability/server/domain/services/compute_error_budget.test.ts @@ -169,12 +169,12 @@ describe('computeErrorBudget', () => { it('computes the error budget with rounded values', () => { const slo = createSLO(); const dateRange = toDateRange(slo.timeWindow); - const errorBudget = computeErrorBudget(slo, { good: 333, total: 777, dateRange }); + const errorBudget = computeErrorBudget(slo, { good: 770, total: 777, dateRange }); expect(errorBudget).toEqual({ initial: 0.001, - consumed: 571.428571, // i.e. 57,142% consumed - remaining: 0, + consumed: 9.009009, // i.e. 900.90% consumed + remaining: -8.009009, // i.e. -800.90% remaining isEstimated: false, }); }); @@ -187,7 +187,7 @@ describe('computeErrorBudget', () => { expect(errorBudget).toEqual({ initial: 0.001, consumed: 1000, // i.e. 100,000% consumed - remaining: 0, + remaining: -999, isEstimated: false, }); }); diff --git a/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts b/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts index 61da9597c2db9..3302ac35678ee 100644 --- a/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts +++ b/x-pack/plugins/observability/server/domain/services/compute_error_budget.ts @@ -95,7 +95,7 @@ export function toErrorBudget( return { initial: toHighPrecision(initial), consumed: toHighPrecision(consumed), - remaining: Math.max(toHighPrecision(1 - consumed), 0), + remaining: toHighPrecision(1 - consumed), isEstimated, }; } diff --git a/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap b/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap index a8950a810a9b7..323abf7c24d9f 100644 --- a/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/__snapshots__/historical_summary_client.test.ts.snap @@ -2,7 +2,7 @@ exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 1`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.004019, "initial": 0.05, @@ -16,7 +16,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 2`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.023374, "initial": 0.05, @@ -30,7 +30,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 3`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.042725, "initial": 0.05, @@ -44,7 +44,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 4`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.06208, "initial": 0.05, @@ -58,7 +58,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 5`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.081433, "initial": 0.05, @@ -72,7 +72,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 6`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.100784, "initial": 0.05, @@ -86,7 +86,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 7`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.120137, "initial": 0.05, @@ -100,7 +100,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 8`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.139494, "initial": 0.05, @@ -114,7 +114,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 9`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.15887, "initial": 0.05, @@ -128,7 +128,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 10`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.1782, "initial": 0.05, @@ -142,7 +142,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 11`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.197546, "initial": 0.05, @@ -156,7 +156,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 12`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.216933, "initial": 0.05, @@ -170,7 +170,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 13`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.236292, "initial": 0.05, @@ -184,7 +184,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 14`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.25563, "initial": 0.05, @@ -198,7 +198,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 15`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.274977, "initial": 0.05, @@ -212,7 +212,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 16`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.294298, "initial": 0.05, @@ -226,7 +226,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 17`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.313653, "initial": 0.05, @@ -240,7 +240,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Occurrences SLOs returns the summary 18`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.333025, "initial": 0.05, @@ -254,7 +254,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 1`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.001344, "initial": 0.05, @@ -268,7 +268,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 2`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.002688, "initial": 0.05, @@ -282,7 +282,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 3`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.004032, "initial": 0.05, @@ -296,7 +296,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 4`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.005376, "initial": 0.05, @@ -310,7 +310,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 5`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.00672, "initial": 0.05, @@ -324,7 +324,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 6`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.008065, "initial": 0.05, @@ -338,7 +338,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 7`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.009409, "initial": 0.05, @@ -352,7 +352,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 8`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.010753, "initial": 0.05, @@ -366,7 +366,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 9`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.012097, "initial": 0.05, @@ -380,7 +380,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 10`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.013441, "initial": 0.05, @@ -394,7 +394,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 11`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.014785, "initial": 0.05, @@ -408,7 +408,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 12`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.016129, "initial": 0.05, @@ -422,7 +422,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 13`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.017473, "initial": 0.05, @@ -436,7 +436,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 14`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.018817, "initial": 0.05, @@ -450,7 +450,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 15`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.020161, "initial": 0.05, @@ -464,7 +464,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 16`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.021505, "initial": 0.05, @@ -478,7 +478,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 17`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.022849, "initial": 0.05, @@ -492,7 +492,7 @@ Object { exports[`FetchHistoricalSummary Calendar Aligned and Timeslices SLOs returns the summary 18`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.024194, "initial": 0.05, @@ -506,7 +506,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 1`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -520,7 +520,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 2`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -534,7 +534,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 3`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -548,7 +548,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 4`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -562,7 +562,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 5`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -576,7 +576,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 6`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -590,7 +590,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 7`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -604,7 +604,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 8`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -618,7 +618,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 9`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -632,7 +632,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 10`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -646,7 +646,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 11`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -660,7 +660,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 12`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -674,7 +674,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 13`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -688,7 +688,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 14`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -702,7 +702,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 15`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -716,7 +716,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 16`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -730,7 +730,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 17`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -744,7 +744,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 18`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -758,7 +758,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 19`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -772,7 +772,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 20`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -786,7 +786,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 21`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -800,7 +800,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 22`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -814,7 +814,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 23`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -828,7 +828,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 24`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -842,7 +842,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 25`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -856,7 +856,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 26`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -870,7 +870,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 27`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -884,7 +884,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 28`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -898,7 +898,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 29`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -912,7 +912,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Occurrences SLOs returns the summary 30`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -926,7 +926,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 1`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -940,7 +940,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 2`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -954,7 +954,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 3`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -968,7 +968,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 4`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -982,7 +982,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 5`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -996,7 +996,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 6`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1010,7 +1010,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 7`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1024,7 +1024,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 8`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1038,7 +1038,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 9`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1052,7 +1052,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 10`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1066,7 +1066,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 11`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1080,7 +1080,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 12`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1094,7 +1094,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 13`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1108,7 +1108,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 14`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1122,7 +1122,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 15`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1136,7 +1136,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 16`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1150,7 +1150,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 17`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1164,7 +1164,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 18`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1178,7 +1178,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 19`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1192,7 +1192,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 20`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1206,7 +1206,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 21`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1220,7 +1220,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 22`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1234,7 +1234,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 23`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1248,7 +1248,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 24`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1262,7 +1262,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 25`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1276,7 +1276,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 26`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1290,7 +1290,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 27`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1304,7 +1304,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 28`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1318,7 +1318,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 29`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, @@ -1332,7 +1332,7 @@ Object { exports[`FetchHistoricalSummary Rolling and Timeslices SLOs returns the summary 30`] = ` Object { - "date": Any, + "date": Any, "errorBudget": Object { "consumed": 0.6, "initial": 0.05, diff --git a/x-pack/plugins/observability/server/services/slo/__snapshots__/summary_client.test.ts.snap b/x-pack/plugins/observability/server/services/slo/__snapshots__/summary_client.test.ts.snap index e7b4a068d7ae0..56bb33309ea7d 100644 --- a/x-pack/plugins/observability/server/services/slo/__snapshots__/summary_client.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/__snapshots__/summary_client.test.ts.snap @@ -19,7 +19,7 @@ Object { "consumed": 100, "initial": 0.001, "isEstimated": false, - "remaining": 0, + "remaining": -99, }, "sliValue": 0.9, "status": "VIOLATED", @@ -32,7 +32,7 @@ Object { "consumed": 2, "initial": 0.05, "isEstimated": false, - "remaining": 0, + "remaining": -1, }, "sliValue": 0.9, "status": "VIOLATED", diff --git a/x-pack/plugins/observability/server/services/slo/historical_summary_client.test.ts b/x-pack/plugins/observability/server/services/slo/historical_summary_client.test.ts index b55ca69ed3bb2..766af6be9417f 100644 --- a/x-pack/plugins/observability/server/services/slo/historical_summary_client.test.ts +++ b/x-pack/plugins/observability/server/services/slo/historical_summary_client.test.ts @@ -104,9 +104,14 @@ describe('FetchHistoricalSummary', () => { let esClientMock: ElasticsearchClientMock; beforeEach(() => { + jest.useFakeTimers().setSystemTime(new Date('2023-01-18T15:00:00.000Z')); esClientMock = elasticsearchServiceMock.createElasticsearchClient(); }); + afterAll(() => { + jest.useRealTimers(); + }); + describe('Rolling and Occurrences SLOs', () => { it('returns the summary', async () => { const slo = createSLO({ diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx index 2864410593cba..51b441a8a9c93 100644 --- a/x-pack/plugins/osquery/public/components/app.tsx +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -8,11 +8,15 @@ import React from 'react'; import { EuiLoadingElastic, + EuiLoadingSpinner, EuiPage, EuiPageBody, EuiPageContent_Deprecated as EuiPageContent, } from '@elastic/eui'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import useObservable from 'react-use/lib/useObservable'; +import { of } from 'rxjs'; import { Container, Wrapper } from './layouts'; import { OsqueryAppRoutes } from '../routes'; import { useOsqueryIntegrationStatus } from '../common/hooks'; @@ -20,8 +24,9 @@ import { OsqueryAppEmptyState } from './empty_state'; import { MainNavigation } from './main_navigation'; const OsqueryAppComponent = () => { + const { customBranding } = useKibana().services; const { data: osqueryIntegration, isFetched } = useOsqueryIntegrationStatus(); - + const hasCustomBranding = useObservable(customBranding?.hasCustomBranding$ || of(false), false); if (!isFetched) { return ( @@ -33,7 +38,11 @@ const OsqueryAppComponent = () => { color="subdued" hasShadow={false} > - + {hasCustomBranding ? ( + + ) : ( + + )} diff --git a/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts new file mode 100644 index 0000000000000..eabdf998d7b1a --- /dev/null +++ b/x-pack/plugins/osquery/server/handlers/action/create_queries.test.ts @@ -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 { createDynamicQueries, createQueries } from './create_queries'; +import type { Ecs } from '@kbn/core/server'; +import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; + +describe('create queries', () => { + const defualtQueryParams = { + interval: 3600, + platform: 'linux', + version: '1.0.0', + ecs_mapping: {}, + removed: false, + snapshot: true, + }; + const mockedQueriesParams = { + queries: [ + { + query: 'SELECT * FROM processes where pid={{process.pid}};', + id: 'process_with_params', + ...defualtQueryParams, + }, + { + query: 'SELECT * FROM processes where pid={{process.not-existing}};', + id: 'process_wrong_params', + ...defualtQueryParams, + }, + { + query: 'SELECT * FROM processes;', + id: 'process_no_params', + ...defualtQueryParams, + }, + ], + agent_ids: ['929be3ee-13ee-4219-bcc2-5aa1593e8193'], + alert_ids: ['72ae3004b99b747e26c81ae7e4bd978677ec5973234674fef6e4993fa54c9acc'], + }; + const mockedSingleQueryParams = { + query: 'SELECT * FROM processes where pid={{process.pid}};', + interval: 3600, + id: 'process_with_params', + platform: 'linux', + }; + + // Info: getting queries by index (eg. [1], [0]) because can't compare whole query object due to unique action_id generated. + describe('dynamic', () => { + const pid = 123; + it('if queries length it should return replaced list of queries', async () => { + const queries = await createDynamicQueries( + mockedQueriesParams, + { + process: { + pid, + }, + } as Ecs, + {} as OsqueryAppContext + ); + expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); + expect(queries[1].error).toBe( + "This query hasn't been called due to parameter used and its value not found in the alert." + ); + expect(queries[1].query).toBe('SELECT * FROM processes where pid={{process.not-existing}};'); + expect(queries[2].query).toBe('SELECT * FROM processes;'); + }); + it('if single query it should return one replaced query ', async () => { + const queries = await createDynamicQueries( + mockedSingleQueryParams, + { + process: { + pid, + }, + } as Ecs, + {} as OsqueryAppContext + ); + expect(queries[0].query).toBe(`SELECT * FROM processes where pid=${pid};`); + }); + it('if single query with not existing parameter it should return query as it is', async () => { + const queries = await createDynamicQueries( + mockedSingleQueryParams, + { + process: {}, + } as Ecs, + {} as OsqueryAppContext + ); + expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); + expect(queries[0].error).toBe(undefined); + }); + }); + describe('normal', () => { + const TEST_AGENT = 'test-agent'; + it('if queries length it should return not replaced list of queries with agents', async () => { + const queries = await createQueries( + mockedQueriesParams, + [TEST_AGENT], + {} as OsqueryAppContext + ); + expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); + expect(queries[0].agents).toContain(TEST_AGENT); + expect(queries[2].query).toBe('SELECT * FROM processes;'); + expect(queries[2].agents).toContain(TEST_AGENT); + }); + + it('if single query should return not replaced query with agents', async () => { + const queries = await createQueries( + mockedSingleQueryParams, + [TEST_AGENT], + {} as OsqueryAppContext + ); + expect(queries[0].query).toBe('SELECT * FROM processes where pid={{process.pid}};'); + expect(queries[0].agents).toContain(TEST_AGENT); + }); + }); +}); diff --git a/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js b/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js index 9f28f87b492c5..b8ed10cda9375 100644 --- a/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js +++ b/x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js @@ -54,23 +54,21 @@ async function generate() { {} ); - await Promise.all([ - writeFile( - outputFieldMapFilename, - ` + await writeFile( + outputFieldMapFilename, + ` /* This file is generated by x-pack/plugins/rule_registry/scripts/generate_ecs_fieldmap/index.js, do not manually edit */ - export const ecsFieldMap = ${JSON.stringify(fields, null, 2)} as const + export const ecsFieldMap = ${JSON.stringify(fields, null, 2)} as const - export type EcsFieldMap = typeof ecsFieldMap; - `, - { encoding: 'utf-8' } - ).then(() => { - return exec(`node scripts/eslint --fix ${outputFieldMapFilename}`); - }), - ]); + export type EcsFieldMap = typeof ecsFieldMap; + `, + { encoding: 'utf-8' } + ); + + await exec(`node scripts/eslint --fix ${outputFieldMapFilename}`); } generate().catch((err) => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index ff0ec174b0846..8dfa02b0f93af 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -594,8 +594,8 @@ export class AlertsClient { let activeAlertCount = 0; let recoveredAlertCount = 0; ( - (responseAlertSum.aggregations?.count as estypes.AggregationsMultiBucketAggregateBase) - .buckets as estypes.AggregationsStringTermsBucketKeys[] + ((responseAlertSum.aggregations?.count as estypes.AggregationsMultiBucketAggregateBase) + ?.buckets as estypes.AggregationsStringTermsBucketKeys[]) ?? [] ).forEach((b) => { if (b.key === ALERT_STATUS_ACTIVE) { activeAlertCount = b.doc_count; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 7e16fcdd8cdf3..e3678e1455527 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -75,7 +75,7 @@ export interface LifecycleAlertServices< alertWithLifecycle: LifecycleAlertService; getAlertStartedDate: (alertInstanceId: string) => string | null; getAlertUuid: (alertInstanceId: string) => string; - getAlertByAlertUuid: (alertUuid: string) => { [x: string]: any } | null; + getAlertByAlertUuid: (alertUuid: string) => Promise | null>; } export type LifecycleRuleExecutor< diff --git a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts index 721110db4d6af..9324bcfd76cb4 100644 --- a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts @@ -37,5 +37,5 @@ export const createLifecycleAlertServicesMock = < alertWithLifecycle: ({ id }) => alertServices.alertFactory.create(id), getAlertStartedDate: jest.fn((id: string) => null), getAlertUuid: jest.fn((id: string) => 'mock-alert-uuid'), - getAlertByAlertUuid: jest.fn((id: string) => null), + getAlertByAlertUuid: jest.fn((id: string) => Promise.resolve(null)), }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index e6d6403460e50..090c7e6854aeb 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -15,6 +15,8 @@ import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { KibanaFeature } from '@kbn/features-plugin/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import type { Space } from '@kbn/spaces-plugin/public'; +import { spacesManagerMock } from '@kbn/spaces-plugin/public/spaces_manager/mocks'; +import { getUiApi } from '@kbn/spaces-plugin/public/ui_api'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { licenseMock } from '../../../../common/licensing/index.mock'; @@ -23,9 +25,14 @@ import { userAPIClientMock } from '../../users/index.mock'; import { createRawKibanaPrivileges } from '../__fixtures__/kibana_privileges'; import { indicesAPIClientMock, privilegesAPIClientMock, rolesAPIClientMock } from '../index.mock'; import { EditRolePage } from './edit_role_page'; +import { SimplePrivilegeSection } from './privileges/kibana/simple_privilege_section'; import { SpaceAwarePrivilegeSection } from './privileges/kibana/space_aware_privilege_section'; import { TransformErrorSection } from './privileges/kibana/transform_error_section'; +const spacesManager = spacesManagerMock.create(); +const { getStartServices } = coreMock.createSetup(); +const spacesApiUi = getUiApi({ spacesManager, getStartServices }); + const buildFeatures = () => { return [ new KibanaFeature({ @@ -132,10 +139,12 @@ function getProps({ action, role, canManageSpaces = true, + spacesEnabled = true, }: { action: 'edit' | 'clone'; role?: Role; canManageSpaces?: boolean; + spacesEnabled?: boolean; }) { const rolesAPIClient = rolesAPIClientMock.create(); rolesAPIClient.getRole.mockResolvedValue(role); @@ -162,6 +171,9 @@ function getProps({ const { fatalErrors } = coreMock.createSetup(); const { http, docLinks, notifications } = coreMock.createStart(); http.get.mockImplementation(async (path: any) => { + if (!spacesEnabled) { + throw { response: { status: 404 } }; // eslint-disable-line no-throw-literal + } if (path === '/api/spaces/space') { return buildSpaces(); } @@ -183,6 +195,7 @@ function getProps({ fatalErrors, uiCapabilities: buildUICapabilities(canManageSpaces), history: scopedHistoryMock.create(), + spacesApiUi, }; } @@ -411,6 +424,194 @@ describe('', () => { }); }); + describe('with spaces disabled', () => { + it('can render readonly view when not enough privileges', async () => { + coreStart.application.capabilities = { + ...coreStart.application.capabilities, + roles: { + save: false, + }, + }; + + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expectReadOnlyFormButtons(wrapper); + }); + + it('can render a reserved role', async () => { + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(1); + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expectReadOnlyFormButtons(wrapper); + }); + + it('can render a user defined role', async () => { + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find('[data-test-subj="reservedRoleBadgeTooltip"]')).toHaveLength(0); + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe(true); + expectSaveFormButtons(wrapper); + }); + + it('can render when creating a new role', async () => { + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe( + false + ); + expectSaveFormButtons(wrapper); + }); + + it('redirects back to roles when creating a new role without privileges', async () => { + coreStart.application.capabilities = { + ...coreStart.application.capabilities, + roles: { + save: false, + }, + }; + + const props = getProps({ action: 'edit', spacesEnabled: false }); + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(props.history.push).toHaveBeenCalledWith('/'); + }); + + it('can render when cloning an existing role', async () => { + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expect(wrapper.find('input[data-test-subj="roleFormNameInput"]').prop('disabled')).toBe( + false + ); + expectSaveFormButtons(wrapper); + }); + + it('renders a partial read-only view when there is a transform error', async () => { + const wrapper = mountWithIntl( + + + + ); + + await waitForRender(wrapper); + + expect(wrapper.find(TransformErrorSection)).toHaveLength(1); + expectReadOnlyFormButtons(wrapper); + }); + }); + it('registers fatal error if features endpoint fails unexpectedly', async () => { const error = { response: { status: 500 } }; const getFeatures = jest.fn().mockRejectedValue(error); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index 36d6815493c98..c0459cd157e09 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -470,6 +470,7 @@ export const EditRolePage: FunctionComponent = ({ renders without crashing 1`] = ` }, ] } + spacesApiUi={ + Object { + "components": Object { + "getCopyToSpaceFlyout": [Function], + "getEmbeddableLegacyUrlConflict": [Function], + "getLegacyUrlConflict": [Function], + "getShareToSpaceFlyout": [Function], + "getSpaceAvatar": [Function], + "getSpaceList": [Function], + "getSpacesContextProvider": [Function], + }, + "redirectLegacyUrl": [Function], + "useSpaces": [Function], + } + } uiCapabilities={ Object { "catalogue": Object {}, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.test.tsx index 5b1d06a741ad2..62627073943f0 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.test.tsx @@ -8,13 +8,22 @@ import { shallow } from 'enzyme'; import React from 'react'; +import { coreMock } from '@kbn/core/public/mocks'; +import { spacesManagerMock } from '@kbn/spaces-plugin/public/spaces_manager/mocks'; +import { getUiApi } from '@kbn/spaces-plugin/public/ui_api'; + import type { Role } from '../../../../../../common/model'; import { KibanaPrivileges } from '../../../model'; import { RoleValidator } from '../../validate_role'; import { KibanaPrivilegesRegion } from './kibana_privileges_region'; +import { SimplePrivilegeSection } from './simple_privilege_section'; import { SpaceAwarePrivilegeSection } from './space_aware_privilege_section'; import { TransformErrorSection } from './transform_error_section'; +const spacesManager = spacesManagerMock.create(); +const { getStartServices } = coreMock.createSetup(); +const spacesApiUi = getUiApi({ spacesManager, getStartServices }); + const buildProps = () => { return { role: { @@ -62,12 +71,15 @@ const buildProps = () => { onChange: jest.fn(), validator: new RoleValidator(), canCustomizeSubFeaturePrivileges: true, + spacesEnabled: true, + spacesApiUi, }; }; describe('', () => { it('renders without crashing', () => { - expect(shallow()).toMatchSnapshot(); + const props = buildProps(); + expect(shallow()).toMatchSnapshot(); }); it('renders the space-aware privilege form', () => { @@ -76,6 +88,12 @@ describe('', () => { expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); }); + it('renders simple privilege form when spaces is disabled', () => { + const props = buildProps(); + const wrapper = shallow(); + expect(wrapper.find(SimplePrivilegeSection)).toHaveLength(1); + }); + it('renders the transform error section when the role has a transform error', () => { const props = buildProps(); (props.role as Role)._transform_error = ['kibana']; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx index 612bcf05aab56..62a1a021c0aae 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/kibana_privileges_region.tsx @@ -14,11 +14,13 @@ import type { Role } from '../../../../../../common/model'; import type { KibanaPrivileges } from '../../../model'; import { CollapsiblePanel } from '../../collapsible_panel'; import type { RoleValidator } from '../../validate_role'; +import { SimplePrivilegeSection } from './simple_privilege_section'; import { SpaceAwarePrivilegeSection } from './space_aware_privilege_section'; import { TransformErrorSection } from './transform_error_section'; interface Props { role: Role; + spacesEnabled: boolean; canCustomizeSubFeaturePrivileges: boolean; spaces?: Space[]; uiCapabilities: Capabilities; @@ -42,6 +44,7 @@ export class KibanaPrivilegesRegion extends Component { const { kibanaPrivileges, role, + spacesEnabled, canCustomizeSubFeaturePrivileges, spaces = [], uiCapabilities, @@ -55,17 +58,29 @@ export class KibanaPrivilegesRegion extends Component { return ; } + if (spacesApiUi && spacesEnabled) { + return ( + + ); + } + return ( - ); }; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap new file mode 100644 index 0000000000000..b490dc7cefe26 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap @@ -0,0 +1,171 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders without crashing 1`] = ` + + + + +

+ +

+
+
+ + + } + labelType="label" + > + + + + + +

+ +

+
+ , + "inputDisplay": , + "value": "none", + }, + Object { + "dropdownDisplay": + + + + +

+ +

+
+
, + "inputDisplay": , + "value": "read", + }, + Object { + "dropdownDisplay": + + + + +

+ +

+
+
, + "inputDisplay": , + "value": "all", + }, + Object { + "dropdownDisplay": + + + + +

+ +

+
+
, + "inputDisplay": , + "value": "custom", + }, + ] + } + valueOfSelected="none" + /> +
+
+
+
+`; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/index.ts new file mode 100644 index 0000000000000..bea5a3d2d592f --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/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 { SimplePrivilegeSection } from './simple_privilege_section'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx new file mode 100644 index 0000000000000..72061958ecc35 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/privilege_selector.tsx @@ -0,0 +1,59 @@ +/* + * 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 { EuiSelect } from '@elastic/eui'; +import type { ChangeEvent } from 'react'; +import React, { Component } from 'react'; + +import { NO_PRIVILEGE_VALUE } from '../constants'; + +interface Props { + ['data-test-subj']: string; + availablePrivileges: string[]; + onChange: (privilege: string) => void; + value: string | null; + allowNone?: boolean; + disabled?: boolean; + compressed?: boolean; +} + +export class PrivilegeSelector extends Component { + public state = {}; + + public render() { + const { availablePrivileges, value, disabled, allowNone, compressed } = this.props; + + const options = []; + + if (allowNone) { + options.push({ value: NO_PRIVILEGE_VALUE, text: 'none' }); + } + + options.push( + ...availablePrivileges.map((p) => ({ + value: p, + text: p, + })) + ); + + return ( + + ); + } + + public onChange = (e: ChangeEvent) => { + this.props.onChange(e.target.value); + }; +} diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx new file mode 100644 index 0000000000000..8f5efff64aadd --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -0,0 +1,248 @@ +/* + * 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 { EuiButtonGroupProps } from '@elastic/eui'; +import { EuiButtonGroup, EuiComboBox, EuiSuperSelect } from '@elastic/eui'; +import React from 'react'; + +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; + +import type { Role } from '../../../../../../../common/model'; +import { KibanaPrivileges, SecuredFeature } from '../../../../model'; +import { SimplePrivilegeSection } from './simple_privilege_section'; +import { UnsupportedSpacePrivilegesWarning } from './unsupported_space_privileges_warning'; + +const buildProps = (customProps: any = {}) => { + const features = [ + new SecuredFeature({ + id: 'feature1', + name: 'Feature 1', + app: ['app'], + category: { id: 'foo', label: 'foo' }, + privileges: { + all: { + app: ['app'], + savedObject: { + all: ['foo'], + read: [], + }, + ui: ['app-ui'], + }, + read: { + app: ['app'], + savedObject: { + all: [], + read: [], + }, + ui: ['app-ui'], + }, + }, + }), + ] as SecuredFeature[]; + + const kibanaPrivileges = new KibanaPrivileges( + { + features: { + feature1: { + all: ['*'], + read: ['read'], + }, + }, + global: {}, + space: {}, + reserved: {}, + }, + features + ); + + const role = { + name: '', + elasticsearch: { + cluster: ['manage'], + indices: [], + run_as: [], + }, + kibana: [], + ...customProps.role, + }; + + return { + editable: true, + kibanaPrivileges, + features, + onChange: jest.fn(), + canCustomizeSubFeaturePrivileges: true, + ...customProps, + role, + }; +}; + +describe('', () => { + it('renders without crashing', () => { + expect(shallowWithIntl()).toMatchSnapshot(); + }); + + it('displays "none" when no privilege is selected', () => { + const props = buildProps(); + const wrapper = shallowWithIntl(); + const selector = wrapper.find(EuiSuperSelect); + expect(selector.props()).toMatchObject({ + valueOfSelected: 'none', + }); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + + it('displays "custom" when feature privileges are customized', () => { + const props = buildProps({ + role: { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + base: [], + feature: { + feature1: ['foo'], + }, + }, + ], + }, + }); + const wrapper = shallowWithIntl(); + const selector = wrapper.find(EuiSuperSelect); + expect(selector.props()).toMatchObject({ + valueOfSelected: 'custom', + }); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + + it('displays the selected privilege', () => { + const props = buildProps({ + role: { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + base: ['read'], + feature: {}, + }, + ], + }, + }); + const wrapper = shallowWithIntl(); + const selector = wrapper.find(EuiSuperSelect); + expect(selector.props()).toMatchObject({ + valueOfSelected: 'read', + }); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + + it('displays the reserved privilege', () => { + const props = buildProps({ + role: { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + base: [], + feature: {}, + _reserved: ['foo'], + }, + ], + }, + }); + const wrapper = shallowWithIntl(); + const selector = wrapper.find(EuiComboBox); + expect(selector.props()).toMatchObject({ + isDisabled: true, + selectedOptions: [{ label: 'foo' }], + }); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + + it('fires its onChange callback when the privilege changes', () => { + const props = buildProps(); + const wrapper = mountWithIntl(); + const selector = wrapper.find(EuiSuperSelect); + (selector.props() as any).onChange('all'); + + expect(props.onChange).toHaveBeenCalledWith({ + name: '', + elasticsearch: { + cluster: ['manage'], + indices: [], + run_as: [], + }, + kibana: [{ feature: {}, base: ['all'], spaces: ['*'] }], + }); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + + it('allows feature privileges to be customized', () => { + const props = buildProps({ + onChange: (role: Role) => { + wrapper.setProps({ + role, + }); + }, + }); + const wrapper = mountWithIntl(); + const selector = wrapper.find(EuiSuperSelect); + (selector.props() as any).onChange('custom'); + + wrapper.update(); + + const featurePrivilegeToggles = wrapper.find(EuiButtonGroup); + expect(featurePrivilegeToggles).toHaveLength(1); + expect(featurePrivilegeToggles.find('input')).toHaveLength(3); + + (featurePrivilegeToggles.props() as EuiButtonGroupProps).onChange('feature1_all', null); + + wrapper.update(); + + expect(wrapper.props().role).toEqual({ + elasticsearch: { + cluster: ['manage'], + indices: [], + run_as: [], + }, + kibana: [ + { + base: [], + feature: { + feature1: ['all'], + }, + spaces: ['*'], + }, + ], + name: '', + }); + + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + + it('renders a warning when space privileges are found', () => { + const props = buildProps({ + role: { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + base: ['read'], + feature: {}, + }, + { + spaces: ['marketing'], + base: ['read'], + feature: {}, + }, + ], + }, + }); + const wrapper = mountWithIntl(); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx new file mode 100644 index 0000000000000..f886de819e144 --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -0,0 +1,334 @@ +/* + * 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 { + EuiComboBox, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; +import React, { Component, Fragment } from 'react'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +import type { Role, RoleKibanaPrivilege } from '../../../../../../../common/model'; +import { copyRole } from '../../../../../../../common/model'; +import type { KibanaPrivileges } from '../../../../model'; +import { isGlobalPrivilegeDefinition } from '../../../privilege_utils'; +import { CUSTOM_PRIVILEGE_VALUE, NO_PRIVILEGE_VALUE } from '../constants'; +import { FeatureTable } from '../feature_table'; +import { PrivilegeFormCalculator } from '../privilege_form_calculator'; +import { UnsupportedSpacePrivilegesWarning } from './unsupported_space_privileges_warning'; + +interface Props { + role: Role; + kibanaPrivileges: KibanaPrivileges; + onChange: (role: Role) => void; + editable: boolean; + canCustomizeSubFeaturePrivileges: boolean; +} + +interface State { + isCustomizingGlobalPrivilege: boolean; + globalPrivsIndex: number; +} + +export class SimplePrivilegeSection extends Component { + constructor(props: Props) { + super(props); + + const globalPrivs = this.locateGlobalPrivilege(props.role); + const globalPrivsIndex = this.locateGlobalPrivilegeIndex(props.role); + + this.state = { + isCustomizingGlobalPrivilege: Boolean( + globalPrivs && Object.keys(globalPrivs.feature).length > 0 + ), + globalPrivsIndex, + }; + } + public render() { + const kibanaPrivilege = this.getDisplayedBasePrivilege(); + + const reservedPrivileges = this.props.role.kibana[this.state.globalPrivsIndex]?._reserved ?? []; + + const title = ( + + ); + + const description = ( +

+ +

+ ); + + return ( + + + + + {description} + + + + + {reservedPrivileges.length > 0 ? ( + ({ label: rp }))} + isDisabled + /> + ) : ( + + ), + dropdownDisplay: ( + <> + + + + +

+ +

+
+ + ), + }, + { + value: 'read', + inputDisplay: ( + + ), + dropdownDisplay: ( + <> + + + + +

+ +

+
+ + ), + }, + { + value: 'all', + inputDisplay: ( + + ), + dropdownDisplay: ( + <> + + + + +

+ +

+
+ + ), + }, + { + value: CUSTOM_PRIVILEGE_VALUE, + inputDisplay: ( + + ), + dropdownDisplay: ( + <> + + + + +

+ +

+
+ + ), + }, + ]} + hasDividers + valueOfSelected={kibanaPrivilege} + /> + )} +
+ {this.state.isCustomizingGlobalPrivilege && ( + + + isGlobalPrivilegeDefinition(k) + )} + canCustomizeSubFeaturePrivileges={this.props.canCustomizeSubFeaturePrivileges} + allSpacesSelected + disabled={!this.props.editable} + /> + + )} + {this.maybeRenderSpacePrivilegeWarning()} +
+
+
+ ); + } + + public getDisplayedBasePrivilege = () => { + if (this.state.isCustomizingGlobalPrivilege) { + return CUSTOM_PRIVILEGE_VALUE; + } + + const { role } = this.props; + + const form = this.locateGlobalPrivilege(role); + + return form && form.base.length > 0 ? form.base[0] : NO_PRIVILEGE_VALUE; + }; + + public onKibanaPrivilegeChange = (privilege: string) => { + const role = copyRole(this.props.role); + + const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role); + + if (privilege === NO_PRIVILEGE_VALUE) { + // Remove global entry if no privilege value + role.kibana = role.kibana.filter((entry) => !isGlobalPrivilegeDefinition(entry)); + } else if (privilege === CUSTOM_PRIVILEGE_VALUE) { + // Remove base privilege if customizing feature privileges + form.base = []; + } else { + form.base = [privilege]; + form.feature = {}; + } + + this.props.onChange(role); + this.setState({ + isCustomizingGlobalPrivilege: privilege === CUSTOM_PRIVILEGE_VALUE, + globalPrivsIndex: role.kibana.indexOf(form), + }); + }; + + public onFeaturePrivilegeChange = (featureId: string, privileges: string[]) => { + const role = copyRole(this.props.role); + const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role); + if (privileges.length > 0) { + form.feature[featureId] = [...privileges]; + } else { + delete form.feature[featureId]; + } + this.props.onChange(role); + }; + + private onChangeAllFeaturePrivileges = (privileges: string[]) => { + const role = copyRole(this.props.role); + + const form = this.locateGlobalPrivilege(role) || this.createGlobalPrivilegeEntry(role); + if (privileges.length > 0) { + this.props.kibanaPrivileges.getSecuredFeatures().forEach((feature) => { + form.feature[feature.id] = [...privileges]; + }); + } else { + form.feature = {}; + } + this.props.onChange(role); + }; + + private maybeRenderSpacePrivilegeWarning = () => { + const kibanaPrivileges = this.props.role.kibana; + const hasSpacePrivileges = kibanaPrivileges.some( + (privilege) => !isGlobalPrivilegeDefinition(privilege) + ); + + if (hasSpacePrivileges) { + return ( + + + + ); + } + return null; + }; + + private locateGlobalPrivilegeIndex = (role: Role) => { + return role.kibana.findIndex((privileges) => isGlobalPrivilegeDefinition(privileges)); + }; + + private locateGlobalPrivilege = (role: Role) => { + const spacePrivileges = role.kibana; + return spacePrivileges.find((privileges) => isGlobalPrivilegeDefinition(privileges)); + }; + + private createGlobalPrivilegeEntry(role: Role): RoleKibanaPrivilege { + const newEntry = { + spaces: ['*'], + base: [], + feature: {}, + }; + + role.kibana.push(newEntry); + + return newEntry; + } +} diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.tsx new file mode 100644 index 0000000000000..b3350a314349a --- /dev/null +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/unsupported_space_privileges_warning.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 { EuiCallOut } from '@elastic/eui'; +import React, { Component } from 'react'; + +import { FormattedMessage } from '@kbn/i18n-react'; + +export class UnsupportedSpacePrivilegesWarning extends Component<{}, {}> { + public render() { + return ; + } + + private getMessage = () => { + return ( + + ); + }; +} diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index cc96446a33f2d..60887dee56d97 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -78,7 +78,7 @@ export class SpaceAwarePrivilegeSection extends Component { public render() { const { uiCapabilities } = this.props; - if (!uiCapabilities.spaces.manage) { + if (!uiCapabilities.spaces?.manage) { return ( { @@ -235,12 +236,36 @@ describe('actions schemas', () => { }); describe('NoParametersRequestSchema', () => { - it('should require at least 1 Endpoint ID', () => { + it('should not accept when no endpoint_ids', () => { expect(() => { NoParametersRequestSchema.body.validate({}); }).toThrow(); }); + it('should require at least 1 endpoint id', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: [], + }); + }).toThrow(); + }); + + it('should not accept empty endpoint id', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: [''], + }); + }).toThrow(); + }); + + it('should not accept any empty endpoint_ids in the array', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: ['x', ' ', 'y'], + }); + }).toThrow(); + }); + it('should accept an Endpoint ID as the only required field', () => { expect(() => { NoParametersRequestSchema.body.validate({ @@ -258,6 +283,15 @@ describe('actions schemas', () => { }).not.toThrow(); }); + it('should not accept empty alert IDs', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: ['ABC-XYZ-000'], + alert_ids: [' '], + }); + }).toThrow(); + }); + it('should accept alert IDs', () => { expect(() => { NoParametersRequestSchema.body.validate({ @@ -267,6 +301,15 @@ describe('actions schemas', () => { }).not.toThrow(); }); + it('should not accept empty case IDs', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: ['ABC-XYZ-000'], + case_ids: [' '], + }); + }).toThrow(); + }); + it('should accept case IDs', () => { expect(() => { NoParametersRequestSchema.body.validate({ @@ -278,9 +321,33 @@ describe('actions schemas', () => { }); describe('KillOrSuspendProcessRequestSchema', () => { - it('should require at least 1 Endpoint ID', () => { + it('should not accept when no endpoint_ids', () => { expect(() => { - NoParametersRequestSchema.body.validate({}); + KillOrSuspendProcessRequestSchema.body.validate({}); + }).toThrow(); + }); + + it('should not accept empty endpoint_ids array', () => { + expect(() => { + KillOrSuspendProcessRequestSchema.body.validate({ + endpoint_ids: [], + }); + }).toThrow(); + }); + + it('should not accept empty string as endpoint id', () => { + expect(() => { + KillOrSuspendProcessRequestSchema.body.validate({ + endpoint_ids: [' '], + }); + }).toThrow(); + }); + + it('should not accept any empty string in endpoint_ids array', () => { + expect(() => { + KillOrSuspendProcessRequestSchema.body.validate({ + endpoint_ids: ['x', ' ', 'y'], + }); }).toThrow(); }); @@ -340,4 +407,106 @@ describe('actions schemas', () => { }).not.toThrow(); }); }); + + describe('ExecuteActionRequestSchema', () => { + it('should not accept when no endpoint_ids', () => { + expect(() => { + NoParametersRequestSchema.body.validate({}); + }).toThrow(); + }); + + it('should not accept empty endpoint_ids array', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: [], + }); + }).toThrow(); + }); + + it('should not accept empty string as endpoint id', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: [' '], + }); + }).toThrow(); + }); + + it('should not accept any empty string in endpoint_ids array', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: ['x', ' ', 'y'], + }); + }).toThrow(); + }); + + it('should not accept an empty command with a valid endpoint_id', () => { + expect(() => { + NoParametersRequestSchema.body.validate({ + endpoint_ids: ['endpoint_id'], + parameters: { + command: ' ', + }, + }); + }).toThrow(); + }); + + it('should accept at least 1 valid endpoint id and a command', () => { + expect(() => { + ExecuteActionRequestSchema.body.validate({ + endpoint_ids: ['endpoint_id'], + parameters: { + command: 'ls -al', + }, + }); + }).not.toThrow(); + }); + + it('should accept at least one endpoint_id and a command parameter', () => { + expect(() => { + ExecuteActionRequestSchema.body.validate({ + endpoint_ids: ['endpoint_id'], + parameters: { + command: 'ls -al', + }, + }); + }).not.toThrow(); + }); + + it('should not accept optional invalid timeout with at least one endpoint_id and a command parameter', () => { + expect(() => { + ExecuteActionRequestSchema.body.validate({ + endpoint_ids: ['endpoint_id'], + parameters: { + command: 'ls -al', + timeout: '', + }, + }); + }).toThrow(); + }); + + it('should also accept a valid timeout with at least one endpoint_id and a command parameter', () => { + expect(() => { + ExecuteActionRequestSchema.body.validate({ + endpoint_ids: ['endpoint_id'], + parameters: { + command: 'ls -al', + timeout: 1000, + }, + }); + }).not.toThrow(); + }); + + it('should also accept an optional comment', () => { + expect(() => { + ExecuteActionRequestSchema.body.validate({ + endpoint_ids: ['endpoint_id'], + parameters: { + command: 'ls -al', + timeout: 1000, + }, + comment: 'a user comment', + }); + }).not.toThrow(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index acd21d56ec6d7..431572412c206 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -15,11 +15,36 @@ import { const BaseActionRequestSchema = { /** A list of endpoint IDs whose hosts will be isolated (Fleet Agent IDs will be retrieved for these) */ - endpoint_ids: schema.arrayOf(schema.string(), { minSize: 1 }), + endpoint_ids: schema.arrayOf(schema.string({ minLength: 1 }), { + minSize: 1, + validate: (endpointIds) => { + if (endpointIds.map((v) => v.trim()).some((v) => !v.length)) { + return 'endpoint_ids cannot contain empty strings'; + } + }, + }), /** If defined, any case associated with the given IDs will be updated */ - alert_ids: schema.maybe(schema.arrayOf(schema.string())), + alert_ids: schema.maybe( + schema.arrayOf(schema.string({ minLength: 1 }), { + minSize: 1, + validate: (alertIds) => { + if (alertIds.map((v) => v.trim()).some((v) => !v.length)) { + return 'alert_ids cannot contain empty strings'; + } + }, + }) + ), /** Case IDs to be updated */ - case_ids: schema.maybe(schema.arrayOf(schema.string())), + case_ids: schema.maybe( + schema.arrayOf(schema.string({ minLength: 1 }), { + minSize: 1, + validate: (caseIds) => { + if (caseIds.map((v) => v.trim()).some((v) => !v.length)) { + return 'case_ids cannot contain empty strings'; + } + }, + }) + ), comment: schema.maybe(schema.string()), parameters: schema.maybe(schema.object({})), }; @@ -149,3 +174,20 @@ export const EndpointActionFileInfoSchema = { }; export type EndpointActionFileInfoParams = TypeOf; + +export const ExecuteActionRequestSchema = { + body: schema.object({ + ...BaseActionRequestSchema, + parameters: schema.object({ + command: schema.string({ + minLength: 1, + validate: (value) => { + if (!value.trim().length) { + return 'command cannot be an empty string'; + } + }, + }), + timeout: schema.maybe(schema.number({ min: 1 })), + }), + }), +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts index 493e5d9ee3d75..257c63825ecf6 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts @@ -138,6 +138,7 @@ describe('Endpoint Authz service', () => { ['canKillProcess', 'writeProcessOperations'], ['canSuspendProcess', 'writeProcessOperations'], ['canGetRunningProcesses', 'writeProcessOperations'], + ['canWriteExecuteOperations', 'writeExecuteOperations'], ['canWriteFileOperations', 'writeFileOperations'], ['canWriteTrustedApplications', 'writeTrustedApplications'], ['canReadTrustedApplications', 'readTrustedApplications'], @@ -168,6 +169,7 @@ describe('Endpoint Authz service', () => { ['canKillProcess', ['writeProcessOperations']], ['canSuspendProcess', ['writeProcessOperations']], ['canGetRunningProcesses', ['writeProcessOperations']], + ['canWriteExecuteOperations', ['writeExecuteOperations']], ['canWriteFileOperations', ['writeFileOperations']], ['canWriteTrustedApplications', ['writeTrustedApplications']], ['canReadTrustedApplications', ['writeTrustedApplications', 'readTrustedApplications']], @@ -213,6 +215,7 @@ describe('Endpoint Authz service', () => { canSuspendProcess: false, canGetRunningProcesses: false, canAccessResponseConsole: false, + canWriteExecuteOperations: false, canWriteFileOperations: false, canWriteTrustedApplications: false, canReadTrustedApplications: false, diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts index 45bc24e42416b..c2a3da0b7c35d 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts @@ -213,6 +213,13 @@ export const calculateEndpointAuthz = ( 'writeFileOperations' ); + const canWriteExecuteOperations = hasKibanaPrivilege( + fleetAuthz, + isEndpointRbacEnabled, + hasEndpointManagementAccess, + 'writeExecuteOperations' + ); + return { canWriteSecuritySolution, canReadSecuritySolution, @@ -235,6 +242,7 @@ export const calculateEndpointAuthz = ( canAccessResponseConsole: isEnterpriseLicense && (canIsolateHost || canWriteProcessOperations || canWriteFileOperations), + canWriteExecuteOperations: canWriteExecuteOperations && isEnterpriseLicense, canWriteFileOperations: canWriteFileOperations && isEnterpriseLicense, // artifacts canWriteTrustedApplications, @@ -270,6 +278,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => { canGetRunningProcesses: false, canAccessResponseConsole: false, canWriteFileOperations: false, + canWriteExecuteOperations: false, canWriteTrustedApplications: false, canReadTrustedApplications: false, canWriteHostIsolationExceptions: false, diff --git a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts index 06072813956b4..c9deb2776fa3b 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/response_actions/constants.ts @@ -17,6 +17,7 @@ export const RESPONSE_ACTION_API_COMMANDS_NAMES = [ 'suspend-process', 'running-processes', 'get-file', + 'execute', ] as const; export type ResponseActionsApiCommandNames = typeof RESPONSE_ACTION_API_COMMANDS_NAMES[number]; @@ -30,6 +31,7 @@ export const ENDPOINT_CAPABILITIES = [ 'suspend_process', 'running_processes', 'get_file', + 'execute', ] as const; export type EndpointCapabilities = typeof ENDPOINT_CAPABILITIES[number]; @@ -45,6 +47,7 @@ export const CONSOLE_RESPONSE_ACTION_COMMANDS = [ 'suspend-process', 'processes', 'get-file', + 'execute', ] as const; export type ConsoleResponseActionCommands = typeof CONSOLE_RESPONSE_ACTION_COMMANDS[number]; 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 2004ae490fe2e..a6192124ce1ff 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -149,9 +149,15 @@ export interface ResponseActionGetFileParameters { path: string; } +export interface ResponseActionsExecuteParameters { + command: string; + timeout?: number; +} + export type EndpointActionDataParameterTypes = | undefined | ResponseActionParametersWithPidOrEntityId + | ResponseActionsExecuteParameters | ResponseActionGetFileParameters; export interface EndpointActionData< diff --git a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts index d6f67e3efe8c5..ab7d097ffcd10 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts @@ -46,6 +46,8 @@ export interface EndpointAuthz { canGetRunningProcesses: boolean; /** If user has permissions to use the Response Actions Console */ canAccessResponseConsole: boolean; + /** If user has write permissions to use execute action */ + canWriteExecuteOperations: boolean; /** If user has write permissions to use file operations */ canWriteFileOperations: boolean; /** if user has write permissions for trusted applications */ diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index 089f5ecacd69b..52ab4a8cb8d73 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -81,6 +81,11 @@ export const allowedExperimentalValues = Object.freeze({ */ responseActionGetFileEnabled: true, + /** + * Enables the `execute` endpoint response action + */ + responseActionExecuteEnabled: false, + /** * Enables top charts on Alerts Page */ diff --git a/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts index 03d7d9634f830..6d07ba6acf563 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/cases/connector_options.cy.ts @@ -73,9 +73,7 @@ describe('Cases connector incident fields', () => { cy.get(CONNECTOR_TITLE).should('have.text', getIbmResilientConnectorOptions().title); cy.get(CONNECTOR_CARD_DETAILS).should( 'have.text', - `${ - getIbmResilientConnectorOptions().title - }Incident Types: ${getIbmResilientConnectorOptions().incidentTypes.join(', ')}Severity: ${ + `Incident Types: ${getIbmResilientConnectorOptions().incidentTypes.join(', ')}Severity: ${ getIbmResilientConnectorOptions().severity }` ); diff --git a/x-pack/plugins/security_solution/cypress/screens/case_details.ts b/x-pack/plugins/security_solution/cypress/screens/case_details.ts index 1874d7f816eda..fcd8b60557fc1 100644 --- a/x-pack/plugins/security_solution/cypress/screens/case_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/case_details.ts @@ -43,9 +43,9 @@ export const CASES_TAGS = (tagName: string) => { export const CASE_USER_ACTION = '[data-test-subj="user-action-markdown"]'; -export const CONNECTOR_CARD_DETAILS = '[data-test-subj="connector-card"]'; +export const CONNECTOR_CARD_DETAILS = '[data-test-subj="connector-card-details"]'; -export const CONNECTOR_TITLE = '[data-test-subj="connector-card"] p.euiTitle'; +export const CONNECTOR_TITLE = '[data-test-subj="connector-card-title"]'; export const DELETE_CASE_CONFIRM_BUTTON = '[data-test-subj="confirmModalConfirmButton"]'; diff --git a/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts b/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts index 94735febd0248..e4d194c11359f 100644 --- a/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts +++ b/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts @@ -14,6 +14,7 @@ const ExperimentalFeaturesServiceMock = { get: jest.fn(() => { const ff: ExperimentalFeatures = { ...allowedExperimentalValues, + responseActionExecuteEnabled: true, }; return ff; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts index 66539807787ff..da0761d5160c7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/api/hooks/use_fetch_rule_by_id_query.ts @@ -35,6 +35,7 @@ export const useFetchRuleByIdQuery = (id: string, options?: UseQueryOptions timeframeOptions.timeframeEnd.add('1', 's').toISOString(), + () => timeframeOptions.timeframeEnd.clone().add('1', 's').toISOString(), [timeframeOptions] ); const isEqlRule = useMemo(() => ruleType === 'eql', [ruleType]); 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 cc36974c3fe69..dbd917107e90c 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 @@ -242,6 +242,15 @@ export const useActionsLogFilter = ({ return false; } + // TODO: remove this when `execute` is no longer behind FF + // planned for 8.8 + if ( + commandName === 'execute' && + !ExperimentalFeaturesService.get().responseActionExecuteEnabled + ) { + return false; + } + return true; }).map((commandName) => ({ key: commandName, diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx index 0327c7c356966..ae8d652aeaeb9 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx @@ -27,7 +27,7 @@ import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../common/endpoint/ import { useUserPrivileges as _useUserPrivileges } from '../../../common/components/user_privileges'; import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; import { waitFor } from '@testing-library/react'; -import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__'; +import { getEndpointAuthzInitialStateMock } from '../../../../common/endpoint/service/authz/mocks'; let mockUseGetEndpointActionList: { isFetched?: boolean; @@ -123,6 +123,7 @@ jest.mock('../../hooks/endpoint/use_get_endpoints_list'); jest.mock('../../../common/experimental_features_service'); jest.mock('../../../common/components/user_privileges'); +const useUserPrivilegesMock = _useUserPrivileges as jest.Mock; let mockUseGetFileInfo: { isFetching?: boolean; @@ -139,12 +140,13 @@ jest.mock('../../hooks/response_actions/use_get_file_info', () => { const mockUseGetEndpointsList = useGetEndpointsList as jest.Mock; -// FLAKY https://github.com/elastic/kibana/issues/145635 -describe.skip('Response actions history', () => { - const useUserPrivilegesMock = _useUserPrivileges as jest.Mock< - ReturnType - >; - +const getBaseMockedActionList = () => ({ + isFetched: true, + isFetching: false, + error: null, + refetch: jest.fn(), +}); +describe('Response actions history', () => { const testPrefix = 'response-actions-list'; let render: ( @@ -155,14 +157,6 @@ describe.skip('Response actions history', () => { let mockedContext: AppContextTestRender; let apiMocks: ReturnType; - const refetchFunction = jest.fn(); - const baseMockedActionList = { - isFetched: true, - isFetching: false, - error: null, - refetch: refetchFunction, - }; - beforeEach(async () => { mockedContext = createAppRootMockRenderer(); ({ history } = mockedContext); @@ -173,7 +167,7 @@ describe.skip('Response actions history', () => { }); mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 13 }), }; @@ -189,20 +183,20 @@ describe.skip('Response actions history', () => { pageSize: 50, total: 50, }); + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock(), + }); }); afterEach(() => { - mockUseGetEndpointActionList = { - ...baseMockedActionList, - }; - jest.clearAllMocks(); - useUserPrivilegesMock.mockImplementation(getUserPrivilegesMockDefaultValue); + mockUseGetEndpointActionList = getBaseMockedActionList(); + useUserPrivilegesMock.mockReset(); }); describe('When index does not exist yet', () => { it('should show global loader when waiting for response', () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), isFetched: false, isFetching: true, }; @@ -211,7 +205,7 @@ describe.skip('Response actions history', () => { }); it('should show empty page when there is no index', () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), error: { body: { statusCode: 404, message: 'index_not_found_exception' }, }, @@ -234,7 +228,7 @@ describe.skip('Response actions history', () => { it('should show empty state when there is no data', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 0 }), }; render(); @@ -295,7 +289,7 @@ describe.skip('Response actions history', () => { }; mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data, }; render({ showHostNames: true }); @@ -316,7 +310,7 @@ describe.skip('Response actions history', () => { }; mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data, }; render({ showHostNames: true }); @@ -339,7 +333,7 @@ describe.skip('Response actions history', () => { }; mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data, }; render({ showHostNames: true }); @@ -367,7 +361,7 @@ describe.skip('Response actions history', () => { it('should update per page rows on the table', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 33 }), }; @@ -398,7 +392,7 @@ describe.skip('Response actions history', () => { it('should show 1-1 record label when only 1 record', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 1 }), }; render(); @@ -446,7 +440,7 @@ describe.skip('Response actions history', () => { it('should contain download link in expanded row for `get-file` action WITH file operation permission', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), }; @@ -475,7 +469,7 @@ describe.skip('Response actions history', () => { it('should show file unavailable for download for `get-file` action WITH file operation permission when file is deleted', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), }; @@ -507,20 +501,14 @@ describe.skip('Response actions history', () => { }); it('should not contain download link in expanded row for `get-file` action when NO file operation permission', async () => { - const privileges = useUserPrivilegesMock(); - - useUserPrivilegesMock.mockImplementationOnce(() => { - return { - ...privileges, - endpointPrivileges: { - ...privileges.endpointPrivileges, - canWriteFileOperations: false, - }, - }; + useUserPrivilegesMock.mockReturnValue({ + endpointPrivileges: getEndpointAuthzInitialStateMock({ + canWriteFileOperations: false, + }), }); mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 1, commands: ['get-file'] }), }; @@ -536,6 +524,7 @@ describe.skip('Response actions history', () => { }); it('should refresh data when autoRefresh is toggled on', async () => { + mockUseGetEndpointActionList = getBaseMockedActionList(); render(); const { getByTestId } = renderResult; @@ -550,16 +539,19 @@ describe.skip('Response actions history', () => { reactTestingLibrary.fireEvent.change(intervalInput, { target: { value: 1 } }); await reactTestingLibrary.waitFor(() => { - expect(refetchFunction).toHaveBeenCalledTimes(3); + expect(mockUseGetEndpointActionList.refetch).toHaveBeenCalledTimes(3); }); }); it('should refresh data when super date picker refresh button is clicked', async () => { + mockUseGetEndpointActionList = getBaseMockedActionList(); render(); const superRefreshButton = renderResult.getByTestId(`${testPrefix}-super-refresh-button`); userEvent.click(superRefreshButton); - expect(refetchFunction).toHaveBeenCalledTimes(1); + await waitFor(() => { + expect(mockUseGetEndpointActionList.refetch).toHaveBeenCalled(); + }); }); it('should set date picker with relative dates', async () => { @@ -592,7 +584,7 @@ describe.skip('Response actions history', () => { it('shows completed status badge for successfully completed actions', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 2 }), }; render(); @@ -609,7 +601,7 @@ describe.skip('Response actions history', () => { it('shows Failed status badge for failed actions', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 2, wasSuccessful: false, status: 'failed' }), }; render(); @@ -623,7 +615,7 @@ describe.skip('Response actions history', () => { it('shows Failed status badge for expired actions', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 2, isCompleted: false, @@ -645,7 +637,7 @@ describe.skip('Response actions history', () => { it('shows Pending status badge for pending actions', async () => { mockUseGetEndpointActionList = { - ...baseMockedActionList, + ...getBaseMockedActionList(), data: await getActionListMock({ actionCount: 2, isCompleted: false, status: 'pending' }), }; render(); @@ -693,6 +685,7 @@ describe.skip('Response actions history', () => { 'suspend-process', 'processes', 'get-file', + 'execute', ]); }); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifact_tabs_in_policy_details.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifact_tabs_in_policy_details.cy.ts new file mode 100644 index 0000000000000..a74eba6274034 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifact_tabs_in_policy_details.cy.ts @@ -0,0 +1,246 @@ +/* + * 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 { getEndpointSecurityPolicyManager } from '../../../../scripts/endpoint/common/roles_users/endpoint_security_policy_manager'; +import { getArtifactsListTestsData } from '../fixtures/artifacts_page'; +import { + createPerPolicyArtifact, + createArtifactList, + removeAllArtifacts, + removeExceptionsList, + yieldFirstPolicyID, +} from '../tasks/artifacts'; +import { loadEndpointDataForEventFiltersIfNeeded } from '../tasks/load_endpoint_data'; +import { login, loginWithCustomRole, loginWithRole, ROLE } from '../tasks/login'; +import { performUserActions } from '../tasks/perform_user_actions'; + +const loginWithPrivilegeAll = () => { + loginWithRole(ROLE.endpoint_security_policy_manager); +}; + +const loginWithPrivilegeRead = (privilegePrefix: string) => { + const roleWithArtifactReadPrivilege = getRoleWithArtifactReadPrivilege(privilegePrefix); + loginWithCustomRole('roleWithArtifactReadPrivilege', roleWithArtifactReadPrivilege); +}; + +const loginWithPrivilegeNone = (privilegePrefix: string) => { + const roleWithoutArtifactPrivilege = getRoleWithoutArtifactPrivilege(privilegePrefix); + loginWithCustomRole('roleWithoutArtifactPrivilege', roleWithoutArtifactPrivilege); +}; + +const getRoleWithArtifactReadPrivilege = (privilegePrefix: string) => { + const endpointSecurityPolicyManagerRole = getEndpointSecurityPolicyManager(); + + return { + ...endpointSecurityPolicyManagerRole, + kibana: [ + { + ...endpointSecurityPolicyManagerRole.kibana[0], + feature: { + ...endpointSecurityPolicyManagerRole.kibana[0].feature, + siem: [ + ...endpointSecurityPolicyManagerRole.kibana[0].feature.siem.filter( + (privilege) => privilege !== `${privilegePrefix}all` + ), + `${privilegePrefix}read`, + ], + }, + }, + ], + }; +}; + +const getRoleWithoutArtifactPrivilege = (privilegePrefix: string) => { + const endpointSecurityPolicyManagerRole = getEndpointSecurityPolicyManager(); + + return { + ...endpointSecurityPolicyManagerRole, + kibana: [ + { + ...endpointSecurityPolicyManagerRole.kibana[0], + feature: { + ...endpointSecurityPolicyManagerRole.kibana[0].feature, + siem: endpointSecurityPolicyManagerRole.kibana[0].feature.siem.filter( + (privilege) => privilege !== `${privilegePrefix}all` + ), + }, + }, + ], + }; +}; + +const visitArtifactTab = (tabId: string) => { + visitPolicyDetailsPage(); + cy.get(`#${tabId}`).click(); +}; + +const visitPolicyDetailsPage = () => { + cy.visit('/app/security/administration/policy'); + cy.getBySel('policyNameCellLink').eq(0).click({ force: true }); + cy.getBySel('policyDetailsPage').should('exist'); + cy.get('#settings').should('exist'); // waiting for Policy Settings tab +}; + +describe('Artifact tabs in Policy Details page', () => { + before(() => { + login(); + loadEndpointDataForEventFiltersIfNeeded(); + }); + + after(() => { + login(); + removeAllArtifacts(); + }); + + for (const testData of getArtifactsListTestsData()) { + beforeEach(() => { + login(); + removeExceptionsList(testData.createRequestBody.list_id); + }); + + describe(`${testData.title} tab`, () => { + it(`[NONE] User cannot see the tab for ${testData.title}`, () => { + loginWithPrivilegeNone(testData.privilegePrefix); + visitPolicyDetailsPage(); + + cy.get(`#${testData.tabId}`).should('not.exist'); + }); + + context(`Given there are no ${testData.title} entries`, () => { + it(`[READ] User CANNOT add ${testData.title} artifact`, () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + cy.getBySel('policy-artifacts-empty-unexisting').should('exist'); + + cy.getBySel('unexisting-manage-artifacts-button').should('not.exist'); + }); + + it(`[ALL] User can add ${testData.title} artifact`, () => { + loginWithPrivilegeAll(); + visitArtifactTab(testData.tabId); + + cy.getBySel('policy-artifacts-empty-unexisting').should('exist'); + + cy.getBySel('unexisting-manage-artifacts-button').should('exist').click(); + + const { formActions, checkResults } = testData.create; + + performUserActions(formActions); + + // Add a per policy artifact - but not assign it to any policy + cy.get('[data-test-subj$="-perPolicy"]').click(); // test-subjects are generated in different formats, but all ends with -perPolicy + cy.getBySel(`${testData.pagePrefix}-flyout-submitButton`).click(); + + // Check new artifact is in the list + for (const checkResult of checkResults) { + cy.getBySel(checkResult.selector).should('have.text', checkResult.value); + } + + cy.getBySel('policyDetailsPage').should('not.exist'); + cy.getBySel('backToOrigin').contains(/^Back to .+ policy$/); + + cy.getBySel('backToOrigin').click(); + cy.getBySel('policyDetailsPage').should('exist'); + }); + }); + + context(`Given there are no assigned ${testData.title} entries`, () => { + beforeEach(() => { + login(); + createArtifactList(testData.createRequestBody.list_id); + createPerPolicyArtifact(testData.artifactName, testData.createRequestBody); + }); + + it(`[READ] User CANNOT Manage or Assign ${testData.title} artifacts`, () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + cy.getBySel('policy-artifacts-empty-unassigned').should('exist'); + + cy.getBySel('unassigned-manage-artifacts-button').should('not.exist'); + cy.getBySel('unassigned-assign-artifacts-button').should('not.exist'); + }); + + it(`[ALL] User can Manage and Assign ${testData.title} artifacts`, () => { + loginWithPrivilegeAll(); + visitArtifactTab(testData.tabId); + + cy.getBySel('policy-artifacts-empty-unassigned').should('exist'); + + // Manage artifacts + cy.getBySel('unassigned-manage-artifacts-button').should('exist').click(); + cy.location('pathname').should( + 'equal', + `/app/security/administration/${testData.urlPath}` + ); + cy.getBySel('backToOrigin').click(); + + // Assign artifacts + cy.getBySel('unassigned-assign-artifacts-button').should('exist').click(); + + cy.getBySel('artifacts-assign-flyout').should('exist'); + cy.getBySel('artifacts-assign-confirm-button').should('be.disabled'); + + cy.getBySel(`${testData.artifactName}_checkbox`).click(); + cy.getBySel('artifacts-assign-confirm-button').click(); + }); + }); + + context(`Given there are assigned ${testData.title} entries`, () => { + beforeEach(() => { + login(); + createArtifactList(testData.createRequestBody.list_id); + yieldFirstPolicyID().then((policyID) => { + createPerPolicyArtifact(testData.artifactName, testData.createRequestBody, policyID); + }); + }); + + it(`[READ] User can see ${testData.title} artifacts but CANNOT assign or remove from policy`, () => { + loginWithPrivilegeRead(testData.privilegePrefix); + visitArtifactTab(testData.tabId); + + // List of artifacts + cy.getBySel('artifacts-collapsed-list-card').should('have.length', 1); + cy.getBySel('artifacts-collapsed-list-card-header-titleHolder').contains( + testData.artifactName + ); + + // Cannot assign artifacts + cy.getBySel('artifacts-assign-button').should('not.exist'); + + // Cannot remove from policy + cy.getBySel('artifacts-collapsed-list-card-header-actions-button').click(); + cy.getBySel('remove-from-policy-action').should('not.exist'); + }); + + it(`[ALL] User can see ${testData.title} artifacts and can assign or remove artifacts from policy`, () => { + loginWithPrivilegeAll(); + visitArtifactTab(testData.tabId); + + // List of artifacts + cy.getBySel('artifacts-collapsed-list-card').should('have.length', 1); + cy.getBySel('artifacts-collapsed-list-card-header-titleHolder').contains( + testData.artifactName + ); + + // Assign artifacts + cy.getBySel('artifacts-assign-button').should('exist').click(); + cy.getBySel('artifacts-assign-flyout').should('exist'); + cy.getBySel('artifacts-assign-cancel-button').click(); + + // Remove from policy + cy.getBySel('artifacts-collapsed-list-card-header-actions-button').click(); + cy.getBySel('remove-from-policy-action').click(); + cy.getBySel('confirmModalConfirmButton').click(); + + cy.contains('Successfully removed'); + }); + }); + }); + } +}); diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts.cy.ts index a094c7a498897..166d506514553 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/artifacts.cy.ts @@ -5,27 +5,12 @@ * 2.0. */ -import { isEmpty } from 'lodash'; -import { - ENDPOINT_ARTIFACT_LIST_IDS, - EXCEPTION_LIST_URL, -} from '@kbn/securitysolution-list-constants'; -import { BASE_ENDPOINT_ROUTE } from '../../../../common/endpoint/constants'; import { login, loginWithRole, ROLE } from '../tasks/login'; -import { type FormAction, getArtifactsListTestsData } from '../fixtures/artifacts_page'; -import { runEndpointLoaderScript } from '../tasks/run_endpoint_loader'; - -const removeAllArtifacts = () => { - for (const listId of ENDPOINT_ARTIFACT_LIST_IDS) { - cy.request({ - method: 'DELETE', - url: `${EXCEPTION_LIST_URL}?list_id=${listId}&namespace_type=agnostic`, - headers: { 'kbn-xsrf': 'kibana' }, - failOnStatusCode: false, - }); - } -}; +import { getArtifactsListTestsData } from '../fixtures/artifacts_page'; +import { removeAllArtifacts } from '../tasks/artifacts'; +import { performUserActions } from '../tasks/perform_user_actions'; +import { loadEndpointDataForEventFiltersIfNeeded } from '../tasks/load_endpoint_data'; const loginWithWriteAccess = (url: string) => { loginWithRole(ROLE.analyst_hunter); @@ -42,41 +27,6 @@ const loginWithoutAccess = (url: string) => { cy.visit(url); }; -// Checks for Endpoint data and creates it if needed -const loadEndpointDataForEventFiltersIfNeeded = () => { - cy.request({ - method: 'POST', - url: `${BASE_ENDPOINT_ROUTE}/suggestions/eventFilters`, - body: { - field: 'agent.type', - query: '', - }, - headers: { 'kbn-xsrf': 'kibana' }, - failOnStatusCode: false, - }).then(({ body }) => { - if (isEmpty(body)) { - runEndpointLoaderScript(); - } - }); -}; - -const runAction = (action: FormAction) => { - let element; - if (action.customSelector) { - element = cy.get(action.customSelector); - } else { - element = cy.getBySel(action.selector || ''); - } - - if (action.type === 'click') { - element.click(); - } else if (action.type === 'input') { - element.type(action.value || ''); - } else if (action.type === 'clear') { - element.clear(); - } -}; - describe('Artifacts pages', () => { before(() => { login(); @@ -117,9 +67,7 @@ describe('Artifacts pages', () => { // Opens add flyout cy.getBySel(`${testData.pagePrefix}-emptyState-addButton`).click(); - for (const formAction of testData.create.formActions) { - runAction(formAction); - } + performUserActions(testData.create.formActions); // Submit create artifact form cy.getBySel(`${testData.pagePrefix}-flyout-submitButton`).click(); @@ -153,9 +101,7 @@ describe('Artifacts pages', () => { cy.getBySel(`${testData.pagePrefix}-card-header-actions-button`).click(); cy.getBySel(`${testData.pagePrefix}-card-cardEditAction`).click(); - for (const formAction of testData.update.formActions) { - runAction(formAction); - } + performUserActions(testData.update.formActions); // Submit edit artifact form cy.getBySel(`${testData.pagePrefix}-flyout-submitButton`).click(); diff --git a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts index 5577879af0241..ec99c404cc62e 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/fixtures/artifacts_page.ts @@ -5,31 +5,49 @@ * 2.0. */ -import type { ArtifactElasticsearchProperties } from '@kbn/fleet-plugin/server/services'; -import type { TranslatedExceptionListItem } from '../../../../server/endpoint/schemas'; +import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants'; +import type { FormAction } from '../tasks/perform_user_actions'; -export interface ArtifactResponseType { - _index: string; - _id: string; - _score: number; - _source: ArtifactElasticsearchProperties; -} +interface FormEditingDescription { + formActions: FormAction[]; -export interface ArtifactBodyType { - entries: TranslatedExceptionListItem[]; + checkResults: Array<{ + selector: string; + value: string; + }>; } -export interface FormAction { - type: string; - selector?: string; - customSelector?: string; - value?: string; +interface ArtifactsFixtureType { + title: string; + pagePrefix: string; + tabId: string; + artifactName: string; + privilegePrefix: string; + urlPath: string; + emptyState: string; + + create: FormEditingDescription; + update: FormEditingDescription; + + delete: { + confirmSelector: string; + card: string; + }; + + createRequestBody: { + list_id: string; + entries: object[]; + os_types: string[]; + }; } -export const getArtifactsListTestsData = () => [ +export const getArtifactsListTestsData = (): ArtifactsFixtureType[] => [ { title: 'Trusted applications', pagePrefix: 'trustedAppsListPage', + tabId: 'trustedApps', + artifactName: 'Trusted application name', + privilegePrefix: 'trusted_applications_', create: { formActions: [ { @@ -122,13 +140,40 @@ export const getArtifactsListTestsData = () => [ confirmSelector: 'trustedAppsListPage-deleteModal-submitButton', card: 'trustedAppsListPage-card', }, - pageObject: 'trustedApplications', urlPath: 'trusted_apps', emptyState: 'trustedAppsListPage-emptyState', + + createRequestBody: { + list_id: ENDPOINT_ARTIFACT_LISTS.trustedApps.id, + entries: [ + { + entries: [ + { + field: 'trusted', + operator: 'included', + type: 'match', + value: 'true', + }, + { + field: 'subject_name', + operator: 'included', + type: 'match', + value: 'abcd', + }, + ], + field: 'process.Ext.code_signature', + type: 'nested', + }, + ], + os_types: ['windows'], + }, }, { title: 'Event Filters', pagePrefix: 'EventFiltersListPage', + tabId: 'eventFilters', + artifactName: 'Event filter name', + privilegePrefix: 'event_filters_', create: { formActions: [ { @@ -222,13 +267,28 @@ export const getArtifactsListTestsData = () => [ confirmSelector: 'EventFiltersListPage-deleteModal-submitButton', card: 'EventFiltersListPage-card', }, - pageObject: 'eventFilters', urlPath: 'event_filters', emptyState: 'EventFiltersListPage-emptyState', + + createRequestBody: { + list_id: ENDPOINT_ARTIFACT_LISTS.eventFilters.id, + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '1.2.3.4', + }, + ], + os_types: ['windows'], + }, }, { title: 'Blocklist', pagePrefix: 'blocklistPage', + tabId: 'blocklists', + artifactName: 'Blocklist name', + privilegePrefix: 'blocklist_', create: { formActions: [ { @@ -330,13 +390,34 @@ export const getArtifactsListTestsData = () => [ confirmSelector: 'blocklistDeletionConfirm', card: 'blocklistCard', }, - pageObject: 'blocklist', urlPath: 'blocklist', emptyState: 'blocklistPage-emptyState', + + createRequestBody: { + list_id: ENDPOINT_ARTIFACT_LISTS.blocklists.id, + entries: [ + { + field: 'file.Ext.code_signature', + entries: [ + { + field: 'subject_name', + value: ['wegwergwegw'], + type: 'match_any', + operator: 'included', + }, + ], + type: 'nested', + }, + ], + os_types: ['windows'], + }, }, { title: 'Host isolation exceptions', pagePrefix: 'hostIsolationExceptionsListPage', + tabId: 'hostIsolationExceptions', + artifactName: 'Host Isolation exception name', + privilegePrefix: 'host_isolation_exceptions_', create: { formActions: [ { @@ -411,8 +492,20 @@ export const getArtifactsListTestsData = () => [ confirmSelector: 'hostIsolationExceptionsDeletionConfirm', card: 'hostIsolationExceptionsCard', }, - pageObject: 'hostIsolationExceptions', urlPath: 'host_isolation_exceptions', emptyState: 'hostIsolationExceptionsListPage-emptyState', + + createRequestBody: { + list_id: ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id, + entries: [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: '1.2.3.4', + }, + ], + os_types: ['windows', 'linux', 'macos'], + }, }, ]; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/artifacts.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/artifacts.ts new file mode 100644 index 0000000000000..53b191fbe3cfb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/artifacts.ts @@ -0,0 +1,93 @@ +/* + * 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 { PACKAGE_POLICY_API_ROOT } from '@kbn/fleet-plugin/common'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import { + ENDPOINT_ARTIFACT_LISTS, + ENDPOINT_ARTIFACT_LIST_IDS, + EXCEPTION_LIST_ITEM_URL, + EXCEPTION_LIST_URL, +} from '@kbn/securitysolution-list-constants'; + +const API_HEADER = { 'kbn-xsrf': 'kibana' }; + +export const removeAllArtifacts = () => { + for (const listId of ENDPOINT_ARTIFACT_LIST_IDS) { + removeExceptionsList(listId); + } +}; + +export const removeExceptionsList = (listId: string) => { + cy.request({ + method: 'DELETE', + url: `${EXCEPTION_LIST_URL}?list_id=${listId}&namespace_type=agnostic`, + headers: API_HEADER, + failOnStatusCode: false, + }).then(({ status }) => { + expect(status).to.be.oneOf([200, 404]); // should either be success or not found + }); +}; + +const ENDPOINT_ARTIFACT_LIST_TYPES = { + [ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: ExceptionListTypeEnum.ENDPOINT, + [ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: ExceptionListTypeEnum.ENDPOINT_EVENTS, + [ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: + ExceptionListTypeEnum.ENDPOINT_HOST_ISOLATION_EXCEPTIONS, + [ENDPOINT_ARTIFACT_LISTS.blocklists.id]: ExceptionListTypeEnum.ENDPOINT_BLOCKLISTS, +}; + +export const createArtifactList = (listId: string) => { + cy.request({ + method: 'POST', + url: EXCEPTION_LIST_URL, + headers: API_HEADER, + body: { + name: listId, + description: 'This is a test list', + list_id: listId, + type: ENDPOINT_ARTIFACT_LIST_TYPES[listId], + namespace_type: 'agnostic', + }, + }).then((response) => { + expect(response.status).to.eql(200); + expect(response.body.list_id).to.eql(listId); + expect(response.body.type).to.eql(ENDPOINT_ARTIFACT_LIST_TYPES[listId]); + }); +}; + +export const createPerPolicyArtifact = (name: string, body: object, policyId?: 'all' | string) => { + cy.request({ + method: 'POST', + url: EXCEPTION_LIST_ITEM_URL, + + headers: API_HEADER, + body: { + name, + description: '', + type: 'simple', + namespace_type: 'agnostic', + ...body, + ...(policyId ? { tags: [`policy:${policyId}`] } : {}), + }, + }).then((response) => { + expect(response.status).to.eql(200); + expect(response.body.name).to.eql(name); + }); +}; + +export const yieldFirstPolicyID = () => { + return cy + .request({ + method: 'GET', + url: `${PACKAGE_POLICY_API_ROOT}?page=1&perPage=1&kuery=ingest-package-policies.package.name: endpoint`, + }) + .then(({ body }) => { + expect(body.items.length).to.be.least(1); + return body.items[0].id; + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/load_endpoint_data.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/load_endpoint_data.ts new file mode 100644 index 0000000000000..5e6650404e29a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/load_endpoint_data.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { isEmpty } from 'lodash'; +import { BASE_ENDPOINT_ROUTE } from '../../../../common/endpoint/constants'; +import { runEndpointLoaderScript } from './run_endpoint_loader'; + +// Checks for Endpoint data and creates it if needed +export const loadEndpointDataForEventFiltersIfNeeded = () => { + cy.request({ + method: 'POST', + url: `${BASE_ENDPOINT_ROUTE}/suggestions/eventFilters`, + body: { + field: 'agent.type', + query: '', + }, + headers: { 'kbn-xsrf': 'kibana' }, + failOnStatusCode: false, + }).then(({ body }) => { + if (isEmpty(body)) { + runEndpointLoaderScript(); + } + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts index 2278e9dad6ac2..7d13090ac0ff8 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/login.ts @@ -19,6 +19,7 @@ import { getSocManager } from '../../../../scripts/endpoint/common/roles_users/s import { getPlatformEngineer } from '../../../../scripts/endpoint/common/roles_users/platform_engineer'; import { getEndpointOperationsAnalyst } from '../../../../scripts/endpoint/common/roles_users/endpoint_operations_analyst'; import { getEndpointSecurityPolicyManager } from '../../../../scripts/endpoint/common/roles_users/endpoint_security_policy_manager'; +import { getDetectionsEngineer } from '../../../../scripts/endpoint/common/roles_users/detections_engineer'; export enum ROLE { t1_analyst = 't1Analyst', @@ -32,7 +33,7 @@ export enum ROLE { endpoint_security_policy_manager = 'endpointSecurityPolicyManager', } -export const rolesMapping: { [id: string]: Omit } = { +export const rolesMapping: { [key in ROLE]: Omit } = { t1Analyst: getT1Analyst(), t2Analyst: getT2Analyst(), hunter: getHunter(), @@ -41,6 +42,7 @@ export const rolesMapping: { [id: string]: Omit } = { platformEngineer: getPlatformEngineer(), endpointOperationsAnalyst: getEndpointOperationsAnalyst(), endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(), + detectionsEngineer: getDetectionsEngineer(), }; /** * Credentials in the `kibana.dev.yml` config file will be used to authenticate @@ -77,6 +79,13 @@ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; */ const LOGIN_API_ENDPOINT = '/internal/security/login'; +const API_AUTH = { + user: Cypress.env(ELASTICSEARCH_USERNAME), + pass: Cypress.env(ELASTICSEARCH_PASSWORD), +}; + +const API_HEADERS = { 'kbn-xsrf': 'cypress' }; + /** * cy.visit will default to the baseUrl which uses the default kibana test user * This function will override that functionality in cy.visit by building the baseUrl @@ -85,7 +94,7 @@ const LOGIN_API_ENDPOINT = '/internal/security/login'; * @param role string role/user to log in with * @param route string route to visit */ -export const getUrlWithRoute = (role: ROLE, route: string) => { +export const getUrlWithRoute = (role: string, route: string) => { const url = Cypress.config().baseUrl; const kibana = new URL(String(url)); const theUrl = `${Url.format({ @@ -138,38 +147,32 @@ export const getCurlScriptEnvVars = () => ({ }); export const createRoleAndUser = (role: ROLE) => { + createCustomRoleAndUser(role, rolesMapping[role]); +}; + +export const createCustomRoleAndUser = (role: string, rolePrivileges: Omit) => { const env = getCurlScriptEnvVars(); // post the role cy.request({ method: 'PUT', url: `${env.KIBANA_URL}/api/security/role/${role}`, - body: rolesMapping[role], - headers: { - 'kbn-xsrf': 'cypress', - }, - auth: { - user: Cypress.env(ELASTICSEARCH_USERNAME), - pass: Cypress.env(ELASTICSEARCH_PASSWORD), - }, + body: rolePrivileges, + headers: API_HEADERS, + auth: API_AUTH, }); // post the user associated with the role to elasticsearch cy.request({ method: 'POST', url: `${env.KIBANA_URL}/internal/security/users/${role}`, - headers: { - 'kbn-xsrf': 'cypress', - }, + headers: API_HEADERS, body: { username: role, password: Cypress.env(ELASTICSEARCH_PASSWORD), roles: [role], }, - auth: { - user: Cypress.env(ELASTICSEARCH_USERNAME), - pass: Cypress.env(ELASTICSEARCH_PASSWORD), - }, + auth: API_AUTH, }); }; @@ -178,24 +181,14 @@ export const deleteRoleAndUser = (role: ROLE) => { cy.request({ method: 'DELETE', - auth: { - user: Cypress.env(ELASTICSEARCH_USERNAME), - pass: Cypress.env(ELASTICSEARCH_PASSWORD), - }, - headers: { - 'kbn-xsrf': 'cypress', - }, + auth: API_AUTH, + headers: API_HEADERS, url: `${env.KIBANA_URL}/internal/security/users/${role}`, }); cy.request({ method: 'DELETE', - auth: { - user: Cypress.env(ELASTICSEARCH_USERNAME), - pass: Cypress.env(ELASTICSEARCH_PASSWORD), - }, - headers: { - 'kbn-xsrf': 'cypress', - }, + auth: API_AUTH, + headers: API_HEADERS, url: `${env.KIBANA_URL}/api/security/role/${role}`, }); }; @@ -220,7 +213,11 @@ export const loginWithUser = (user: User) => { }; export const loginWithRole = async (role: ROLE) => { - createRoleAndUser(role); + loginWithCustomRole(role, rolesMapping[role]); +}; + +export const loginWithCustomRole = async (role: string, rolePrivileges: Omit) => { + createCustomRoleAndUser(role, rolePrivileges); const theUrl = Url.format({ auth: `${role}:changeme`, username: role, diff --git a/x-pack/plugins/security_solution/public/management/cypress/tasks/perform_user_actions.ts b/x-pack/plugins/security_solution/public/management/cypress/tasks/perform_user_actions.ts new file mode 100644 index 0000000000000..69c7bd369cdeb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/cypress/tasks/perform_user_actions.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type ActionTypes = 'click' | 'input' | 'clear'; + +export interface FormAction { + type: ActionTypes; + selector?: string; + customSelector?: string; + value?: string; +} + +export const performUserActions = (actions: FormAction[]) => { + for (const action of actions) { + performAction(action); + } +}; + +const performAction = (action: FormAction) => { + let element; + if (action.customSelector) { + element = cy.get(action.customSelector); + } else { + element = cy.getBySel(action.selector || ''); + } + + if (action.type === 'click') { + element.click(); + } else if (action.type === 'input') { + element.type(action.value || ''); + } else if (action.type === 'clear') { + element.clear(); + } +}; diff --git a/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json b/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json index a99e4af76fe2e..a385fa4c78ec6 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json +++ b/x-pack/plugins/security_solution/public/management/cypress/tsconfig.json @@ -24,6 +24,7 @@ }, "@kbn/security-plugin", "@kbn/securitysolution-list-constants", - "@kbn/fleet-plugin" + "@kbn/fleet-plugin", + "@kbn/securitysolution-io-ts-list-types", ] } diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx index 8b1ab7dee0854..52b9f95ce591b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/blocklist_form.tsx @@ -540,6 +540,7 @@ export const BlockListForm = memo( onChange={handleOnPolicyChange} isLoading={policiesIsLoading} description={POLICY_SELECT_DESCRIPTION} + data-test-subj={getTestId('effectedPolicies')} /> diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts index 5a983574f7545..671347dcd27b3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/mock_endpoint_result_list.ts @@ -156,7 +156,6 @@ const endpointListApiPathHandlerMocks = ({ return { total: totalAgentsUsingEndpoint, items: [], - totalInactive: 0, page: 1, perPage: 10, }; diff --git a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx index 95aed4e29ea3a..24076272f37ae 100644 --- a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx @@ -13,7 +13,6 @@ import type { AppContextTestRender } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { useUserPrivileges } from '../../../common/components/user_privileges'; import { endpointPageHttpMock } from '../endpoint_hosts/mocks'; -import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__'; jest.mock('../../../common/components/user_privileges'); @@ -30,7 +29,7 @@ describe('when in the Administration tab', () => { }); afterEach(() => { - useUserPrivilegesMock.mockImplementation(getUserPrivilegesMockDefaultValue); + useUserPrivilegesMock.mockReset(); }); describe('when the user has no permissions', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx index 8b82608f27783..0abdc7f24e08b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx @@ -379,7 +379,7 @@ describe('Response actions history page', () => { }); expect(history.location.search).toEqual( - '?commands=isolate%2Crelease%2Ckill-process%2Csuspend-process%2Cprocesses%2Cget-file' + '?commands=isolate%2Crelease%2Ckill-process%2Csuspend-process%2Cprocesses%2Cget-file%2Cexecute' ); }); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_security_policy_manager.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_security_policy_manager.ts index 5b57f4e2888bb..0a9679b8dbfd6 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_security_policy_manager.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/endpoint_security_policy_manager.ts @@ -17,7 +17,15 @@ export const getEndpointSecurityPolicyManager: () => Omit = () => ...noResponseActionsRole.kibana[0], feature: { ...noResponseActionsRole.kibana[0].feature, - siem: ['minimal_all'], + siem: [ + 'blocklist_all', + 'endpoint_list_all', + 'event_filters_all', + 'host_isolation_exceptions_all', + 'minimal_all', + 'policy_management_all', + 'trusted_applications_all', + ], }, }, ], diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/with_response_actions_role.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/with_response_actions_role.ts index 4b4a1d2a5c002..c86fbd472adfb 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/with_response_actions_role.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/with_response_actions_role.ts @@ -19,9 +19,10 @@ export const getWithResponseActionsRole: () => Omit = () => { ...noResponseActionsRole.kibana[0].feature, siem: [ ...noResponseActionsRole.kibana[0].feature.siem, + 'file_operations_all', + 'execute_operations_all', 'host_isolation_all', 'process_operations_all', - 'file_operations_all', ], }, }, diff --git a/x-pack/plugins/security_solution/server/config.mock.ts b/x-pack/plugins/security_solution/server/config.mock.ts index 10ea0b588b379..c1faa6f401a1d 100644 --- a/x-pack/plugins/security_solution/server/config.mock.ts +++ b/x-pack/plugins/security_solution/server/config.mock.ts @@ -14,6 +14,8 @@ export const createMockConfig = (): ConfigType => { const enableExperimental: Array = [ // Remove property below once `get-file` FF is enabled or removed 'responseActionGetFileEnabled', + // remove property below once `execute` FF is enabled or removed + 'responseActionExecuteEnabled', ]; return { 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 76c72ef2bba4a..567ded8fae8cd 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 @@ -41,6 +41,7 @@ import { ISOLATE_HOST_ROUTE, UNISOLATE_HOST_ROUTE, GET_FILE_ROUTE, + EXECUTE_ROUTE, } from '../../../../common/endpoint/constants'; import type { ActionDetails, @@ -49,6 +50,7 @@ import type { HostMetadata, LogsEndpointAction, ResponseActionRequestBody, + ResponseActionsExecuteParameters, } from '../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import type { EndpointAuthz } from '../../../../common/endpoint/types/authz'; @@ -564,6 +566,65 @@ describe('Response actions', () => { expect(responseBody.action).toBeUndefined(); }); + it('handles execute', async () => { + const ctx = await callRoute( + EXECUTE_ROUTE, + { + body: { endpoint_ids: ['XYZ'], parameters: { command: 'ls -al', timeout: 1000 } }, + }, + { endpointDsExists: true } + ); + const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index; + const actionDocs: [ + { index: string; body?: LogsEndpointAction }, + { index: string; body?: EndpointAction } + ] = [ + indexDoc.mock.calls[0][0] as estypes.IndexRequest, + indexDoc.mock.calls[1][0] as estypes.IndexRequest, + ]; + + expect(actionDocs[0].index).toEqual(ENDPOINT_ACTIONS_INDEX); + expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX); + expect(actionDocs[0].body!.EndpointActions.data.command).toEqual('execute'); + const parameters = actionDocs[1].body!.data.parameters as ResponseActionsExecuteParameters; + expect(parameters.command).toEqual('ls -al'); + expect(parameters.timeout).toEqual(1000); + expect(actionDocs[1].body!.data.command).toEqual('execute'); + + expect(mockResponse.ok).toBeCalled(); + const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse; + expect(responseBody.action).toBeUndefined(); + }); + + it('handles execute without optional `timeout`', async () => { + const ctx = await callRoute( + EXECUTE_ROUTE, + { + body: { endpoint_ids: ['XYZ'], parameters: { command: 'ls -al' } }, + }, + { endpointDsExists: true } + ); + const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index; + const actionDocs: [ + { index: string; body?: LogsEndpointAction }, + { index: string; body?: EndpointAction } + ] = [ + indexDoc.mock.calls[0][0] as estypes.IndexRequest, + indexDoc.mock.calls[1][0] as estypes.IndexRequest, + ]; + + expect(actionDocs[0].index).toEqual(ENDPOINT_ACTIONS_INDEX); + expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX); + expect(actionDocs[0].body!.EndpointActions.data.command).toEqual('execute'); + const parameters = actionDocs[1].body!.data.parameters as ResponseActionsExecuteParameters; + expect(parameters.command).toEqual('ls -al'); + expect(actionDocs[1].body!.data.command).toEqual('execute'); + + expect(mockResponse.ok).toBeCalled(); + const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse; + expect(responseBody.action).toBeUndefined(); + }); + it('handles errors', async () => { const ErrMessage = 'Uh oh!'; await callRoute( diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts index ac9c71cd931b5..ba9c7dc1bca89 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts @@ -19,6 +19,7 @@ import { NoParametersRequestSchema, KillOrSuspendProcessRequestSchema, EndpointActionGetFileSchema, + ExecuteActionRequestSchema, } from '../../../../common/endpoint/schema/actions'; import { APP_ID } from '../../../../common/constants'; import { @@ -34,6 +35,7 @@ import { UNISOLATE_HOST_ROUTE, ENDPOINT_ACTIONS_INDEX, GET_FILE_ROUTE, + EXECUTE_ROUTE, } from '../../../../common/endpoint/constants'; import type { EndpointAction, @@ -43,6 +45,7 @@ import type { LogsEndpointAction, LogsEndpointActionResponse, ResponseActionParametersWithPidOrEntityId, + ResponseActionsExecuteParameters, } from '../../../../common/endpoint/types'; import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants'; import type { @@ -175,6 +178,22 @@ export function registerResponseActionRoutes( ) ); } + + // `execute` currently behind FF (planned for 8.8) + if (endpointContext.experimentalFeatures.responseActionExecuteEnabled) { + router.post( + { + path: EXECUTE_ROUTE, + validate: ExecuteActionRequestSchema, + options: { authRequired: true, tags: ['access:securitySolution'] }, + }, + withEndpointAuthz( + { all: ['canWriteExecuteOperations'] }, + logger, + responseActionRequestHandler(endpointContext, 'execute') + ) + ); + } } const commandToFeatureKeyMap = new Map([ @@ -184,6 +203,7 @@ const commandToFeatureKeyMap = new Map { ).resolves.toEqual({ agents: ['agent-a'], hosts: { 'agent-a': { name: 'Host-agent-a' } }, - command: 'unisolate', + command: 'kill-process', completedAt: '2022-04-30T16:08:47.449Z', wasSuccessful: true, errors: undefined, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts index 67d527817abf4..4856d00f6e1a4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_list.test.ts @@ -91,7 +91,7 @@ describe('When using `getActionList()', () => { { agents: ['agent-a'], hosts: { 'agent-a': { name: 'Host-agent-a' } }, - command: 'unisolate', + command: 'kill-process', completedAt: '2022-04-30T16:08:47.449Z', wasSuccessful: true, errors: undefined, @@ -171,7 +171,7 @@ describe('When using `getActionList()', () => { 'agent-b': { name: 'Host-agent-b' }, 'agent-x': { name: '' }, }, - command: 'unisolate', + command: 'kill-process', completedAt: undefined, wasSuccessful: false, errors: undefined, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts index 98797c3a7cc67..cafcc058b3cd9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts @@ -72,7 +72,7 @@ describe('When using Actions service utilities', () => { ) ).toEqual({ agents: ['6e6796b0-af39-4f12-b025-fcb06db499e5'], - command: 'unisolate', + command: 'kill-process', comment: expect.any(String), createdAt: '2022-04-27T16:08:47.449Z', createdBy: 'elastic', @@ -92,7 +92,7 @@ describe('When using Actions service utilities', () => { ) ).toEqual({ agents: ['90d62689-f72d-4a05-b5e3-500cad0dc366'], - command: 'unisolate', + command: 'kill-process', comment: expect.any(String), createdAt: '2022-04-27T16:08:47.449Z', createdBy: 'Shanel', @@ -441,24 +441,7 @@ describe('When using Actions service utilities', () => { completedAt: COMPLETED_AT, wasSuccessful: true, errors: undefined, - outputs: { - '456': { - content: { - code: 'ra_get-file_success_done', - contents: [ - { - file_name: 'bad_file.txt', - path: '/some/path/bad_file.txt', - sha256: '9558c5cb39622e9b3653203e772b129d6c634e7dbd7af1b244352fc1d704601f', - size: 1234, - type: 'file', - }, - ], - zip_size: 123, - }, - type: 'json', - }, - }, + outputs: {}, agentState: { '123': { completedAt: '2022-01-05T19:27:23.816Z', diff --git a/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts b/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts index d83066414add2..f736fffffcf17 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts @@ -22,6 +22,7 @@ const FEATURES = { SUSPEND_PROCESS: 'Suspend process', RUNNING_PROCESSES: 'Get running processes', GET_FILE: 'Get file', + EXECUTE: 'Execute command', ALERTS_BY_PROCESS_ANCESTRY: 'Get related alerts by process ancestry', } as const; diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts index e940dfe6d0c6a..5a0b3b1011227 100644 --- a/x-pack/plugins/security_solution/server/features.ts +++ b/x-pack/plugins/security_solution/server/features.ts @@ -117,6 +117,12 @@ const responseActionSubFeatures: SubFeatureConfig[] = [ defaultMessage: 'Response Actions History', } ), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.description', + { + defaultMessage: 'Access the history of response actions performed on endpoints.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -158,6 +164,10 @@ const responseActionSubFeatures: SubFeatureConfig[] = [ name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.hostIsolation', { defaultMessage: 'Host Isolation', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.description', + { defaultMessage: 'Perform the "isolate" and "release" response actions.' } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -188,6 +198,12 @@ const responseActionSubFeatures: SubFeatureConfig[] = [ name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.processOperations', { defaultMessage: 'Process Operations', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.processOperations.description', + { + defaultMessage: 'Perform process-related response actions in the response console.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -215,7 +231,7 @@ const responseActionSubFeatures: SubFeatureConfig[] = [ defaultMessage: 'All Spaces is required for File Operations access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistr.subFeatures.fileOperations', { + name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.fileOperations', { defaultMessage: 'File Operations', }), privilegeGroups: [ @@ -237,6 +253,43 @@ const responseActionSubFeatures: SubFeatureConfig[] = [ }, ], }, + { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.executeOperations.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Execute Operations access.', + } + ), + name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.executeOperations', { + defaultMessage: 'Execute Operations', + }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.executeOperations.description', + { + // TODO: Update this description before 8.8 FF + defaultMessage: 'Perform script execution on the endpoint.', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + api: [`${APP_ID}-writeExecuteOperations`], + id: 'execute_operations_all', + includeIn: 'none', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ui: ['writeExecuteOperations'], + }, + ], + }, + ], + }, ]; const subFeatures: SubFeatureConfig[] = [ @@ -251,6 +304,13 @@ const subFeatures: SubFeatureConfig[] = [ name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.endpointList', { defaultMessage: 'Endpoint List', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.endpointList.description', + { + defaultMessage: + 'Displays all hosts running Elastic Defend and their relevant integration details.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -292,6 +352,13 @@ const subFeatures: SubFeatureConfig[] = [ name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.trustedApplications', { defaultMessage: 'Trusted Applications', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.description', + { + defaultMessage: + 'Helps mitigate conflicts with other software, usually other antivirus or endpoint security applications.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -342,6 +409,13 @@ const subFeatures: SubFeatureConfig[] = [ defaultMessage: 'Host Isolation Exceptions', } ), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.description', + { + defaultMessage: + 'Add specific IP addresses that isolated hosts are still allowed to communicate with, even when isolated from the rest of the network.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -389,6 +463,13 @@ const subFeatures: SubFeatureConfig[] = [ name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.blockList', { defaultMessage: 'Blocklist', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.blockList.description', + { + defaultMessage: + 'Extend Elastic Defend’s protection against malicious processes and protect against potentially harmful applications.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -436,6 +517,13 @@ const subFeatures: SubFeatureConfig[] = [ name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.eventFilters', { defaultMessage: 'Event Filters', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.eventFilters.description', + { + defaultMessage: + 'Filter out endpoint events that you do not need or want stored in Elasticsearch.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -481,8 +569,15 @@ const subFeatures: SubFeatureConfig[] = [ } ), name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.policyManagement', { - defaultMessage: 'Policy Management', + defaultMessage: 'Elastic Defend Policy Management', }), + description: i18n.translate( + 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement.description', + { + defaultMessage: + 'Access the Elastic Defend integration policy to configure protections, event collection, and advanced policy features.', + } + ), privilegeGroups: [ { groupType: 'mutually_exclusive', @@ -531,6 +626,13 @@ function getSubFeatures(experimentalFeatures: ConfigType['experimentalFeatures'] }); } + // behind FF (planned for 8.8) + if (!experimentalFeatures.responseActionExecuteEnabled) { + filteredSubFeatures = filteredSubFeatures.filter((subFeat) => { + return subFeat.name !== 'Execute operations'; + }); + } + return filteredSubFeatures; } diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index e8bccd2aeacf7..f17c9595a6786 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -7,7 +7,12 @@ import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks'; +import { + elasticsearchServiceMock, + httpServerMock, + loggingSystemMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; import { createNewPackagePolicyMock, deletePackagePolicyMock, @@ -84,6 +89,9 @@ describe('ingest_integration tests ', () => { }); describe('package policy init callback (atifacts manifest initialisation tests)', () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const createNewEndpointPolicyInput = (manifest: ManifestSchema) => ({ type: 'endpoint', enabled: true, @@ -106,7 +114,13 @@ describe('ingest_integration tests ', () => { exceptionListClient ); - return callback(createNewPackagePolicyMock(), requestContextMock.convertContext(ctx), req); + return callback( + createNewPackagePolicyMock(), + soClient, + esClient, + requestContextMock.convertContext(ctx), + req + ); }; const TEST_POLICY_ID_1 = 'c6d16e42-c32d-4dce-8a88-113cfe276ad1'; @@ -258,6 +272,8 @@ describe('ingest_integration tests ', () => { }); describe('package policy post create callback', () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyPostCreateCallback(logger, exceptionListClient); const policyConfig = generator.generatePolicyPackagePolicy() as PackagePolicy; @@ -275,6 +291,8 @@ describe('ingest_integration tests ', () => { }; const postCreatedPolicyConfig = await callback( policyConfig, + soClient, + esClient, requestContextMock.convertContext(ctx), req ); @@ -312,6 +330,8 @@ describe('ingest_integration tests ', () => { }; const postCreatedPolicyConfig = await callback( policyConfig, + soClient, + esClient, requestContextMock.convertContext(ctx), req ); @@ -326,6 +346,9 @@ describe('ingest_integration tests ', () => { }); }); describe('package policy update callback (when the license is below platinum)', () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + beforeEach(() => { licenseEmitter.next(Gold); // set license level to gold }); @@ -341,7 +364,7 @@ describe('ingest_integration tests ', () => { const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; await expect(() => - callback(policyConfig, requestContextMock.convertContext(ctx), req) + callback(policyConfig, soClient, esClient, requestContextMock.convertContext(ctx), req) ).rejects.toThrow('Requires Platinum license'); }); it('updates successfully if no paid features are turned on in the policy', async () => { @@ -358,6 +381,8 @@ describe('ingest_integration tests ', () => { policyConfig.inputs[0]!.config!.policy.value = mockPolicy; const updatedPolicyConfig = await callback( policyConfig, + soClient, + esClient, requestContextMock.convertContext(ctx), req ); @@ -366,6 +391,9 @@ describe('ingest_integration tests ', () => { }); describe('package policy update callback (when the license is at least platinum)', () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + beforeEach(() => { licenseEmitter.next(Platinum); // set license level to platinum }); @@ -383,6 +411,8 @@ describe('ingest_integration tests ', () => { policyConfig.inputs[0]!.config!.policy.value = mockPolicy; const updatedPolicyConfig = await callback( policyConfig, + soClient, + esClient, requestContextMock.convertContext(ctx), req ); @@ -391,9 +421,12 @@ describe('ingest_integration tests ', () => { }); describe('package policy delete callback', () => { + const soClient = savedObjectsClientMock.create(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const invokeDeleteCallback = async (): Promise => { const callback = getPackagePolicyDeleteCallback(exceptionListClient); - await callback(deletePackagePolicyMock()); + await callback(deletePackagePolicyMock(), soClient, esClient); }; let removedPolicies: PostDeletePackagePoliciesResponse; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index a2fe485921e6d..a941b8f8aa4ca 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { KibanaRequest, Logger, RequestHandlerContext } from '@kbn/core/server'; +import type { Logger } from '@kbn/core/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { PluginStartContract as AlertsStartContract } from '@kbn/alerting-plugin/server'; import type { @@ -55,10 +55,18 @@ export const getPackagePolicyCreateCallback = ( exceptionsClient: ExceptionListClient | undefined ): PostPackagePolicyCreateCallback => { return async ( - newPackagePolicy: NewPackagePolicy, - context: RequestHandlerContext, - request: KibanaRequest + newPackagePolicy, + soClient, + esClient, + context, + request ): Promise => { + // callback is called outside request context + if (!context || !request) { + logger.debug('PackagePolicyCreateCallback called outside request context. Skipping...'); + return newPackagePolicy; + } + // We only care about Endpoint package policies if (!isEndpointPackagePolicy(newPackagePolicy)) { return newPackagePolicy; @@ -140,11 +148,7 @@ export const getPackagePolicyUpdateCallback = ( featureUsageService: FeatureUsageService, endpointMetadataService: EndpointMetadataService ): PutPackagePolicyUpdateCallback => { - return async ( - newPackagePolicy: NewPackagePolicy - // context: RequestHandlerContext, - // request: KibanaRequest - ): Promise => { + return async (newPackagePolicy: NewPackagePolicy): Promise => { if (!isEndpointPackagePolicy(newPackagePolicy)) { return newPackagePolicy; } @@ -174,7 +178,7 @@ export const getPackagePolicyPostCreateCallback = ( return packagePolicy; } - const integrationConfig = packagePolicy?.inputs[0].config?.integration_config; + const integrationConfig = packagePolicy?.inputs[0]?.config?.integration_config; if (integrationConfig && integrationConfig?.value?.eventFilters !== undefined) { createEventFilters( diff --git a/x-pack/plugins/spaces/common/index.ts b/x-pack/plugins/spaces/common/index.ts index 3af87c44f8e0f..13a5d5e5ef5ae 100644 --- a/x-pack/plugins/spaces/common/index.ts +++ b/x-pack/plugins/spaces/common/index.ts @@ -6,7 +6,12 @@ */ export { isReservedSpace } from './is_reserved_space'; -export { MAX_SPACE_INITIALS, SPACE_SEARCH_COUNT_THRESHOLD, ENTER_SPACE_PATH } from './constants'; +export { + MAX_SPACE_INITIALS, + SPACE_SEARCH_COUNT_THRESHOLD, + ENTER_SPACE_PATH, + DEFAULT_SPACE_ID, +} from './constants'; export { addSpaceIdToPath, getSpaceIdFromPath } from './lib/spaces_url_parser'; export type { Space, diff --git a/x-pack/plugins/spaces/server/config.test.ts b/x-pack/plugins/spaces/server/config.test.ts new file mode 100644 index 0000000000000..995d3e44cc3f3 --- /dev/null +++ b/x-pack/plugins/spaces/server/config.test.ts @@ -0,0 +1,56 @@ +/* + * 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('crypto', () => ({ + randomBytes: jest.fn(), + constants: jest.requireActual('crypto').constants, +})); + +jest.mock('@kbn/utils', () => ({ + getLogsPath: () => '/mock/kibana/logs/path', +})); + +import { ConfigSchema } from './config'; + +describe('config schema', () => { + it('generates proper defaults', () => { + expect(ConfigSchema.validate({})).toMatchInlineSnapshot(` + Object { + "enabled": true, + "maxSpaces": 1000, + } + `); + + expect(ConfigSchema.validate({}, { dev: false })).toMatchInlineSnapshot(` + Object { + "enabled": true, + "maxSpaces": 1000, + } + `); + + expect(ConfigSchema.validate({}, { dev: true })).toMatchInlineSnapshot(` + Object { + "enabled": true, + "maxSpaces": 1000, + } + `); + }); + + it('should throw error if spaces is disabled', () => { + expect(() => ConfigSchema.validate({ enabled: false })).toThrow( + '[enabled]: Spaces can only be disabled in development mode' + ); + + expect(() => ConfigSchema.validate({ enabled: false }, { dev: false })).toThrow( + '[enabled]: Spaces can only be disabled in development mode' + ); + }); + + it('should not throw error if spaces is disabled in development mode', () => { + expect(() => ConfigSchema.validate({ enabled: false }, { dev: true })).not.toThrow(); + }); +}); diff --git a/x-pack/plugins/spaces/server/config.ts b/x-pack/plugins/spaces/server/config.ts index 980c034da9b61..5e9b6764e6982 100644 --- a/x-pack/plugins/spaces/server/config.ts +++ b/x-pack/plugins/spaces/server/config.ts @@ -12,6 +12,19 @@ import { schema } from '@kbn/config-schema'; import type { PluginInitializerContext } from '@kbn/core/server'; export const ConfigSchema = schema.object({ + enabled: schema.conditional( + schema.contextRef('dev'), + true, + schema.boolean({ defaultValue: true }), + schema.boolean({ + validate: (rawValue) => { + if (rawValue === false) { + return 'Spaces can only be disabled in development mode'; + } + }, + defaultValue: true, + }) + ), maxSpaces: schema.number({ defaultValue: 1000 }), }); diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index c1a51c5c52442..c79c9d778a096 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -16,7 +16,7 @@ const createMockDebugLogger = () => { return jest.fn(); }; -const createMockConfig = (mockConfig: ConfigType = { maxSpaces: 1000 }) => { +const createMockConfig = (mockConfig: ConfigType = { enabled: true, maxSpaces: 1000 }) => { return ConfigSchema.validate(mockConfig); }; @@ -75,7 +75,7 @@ describe('#getAll', () => { mockCallWithRequestRepository.find.mockResolvedValue({ saved_objects: savedObjects, } as any); - const mockConfig = createMockConfig({ maxSpaces: 1234 }); + const mockConfig = createMockConfig({ enabled: true, maxSpaces: 1234 }); const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); const actualSpaces = await client.getAll(); @@ -182,7 +182,7 @@ describe('#create', () => { total: maxSpaces - 1, } as any); - const mockConfig = createMockConfig({ maxSpaces }); + const mockConfig = createMockConfig({ enabled: true, maxSpaces }); const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); @@ -208,7 +208,7 @@ describe('#create', () => { total: maxSpaces, } as any); - const mockConfig = createMockConfig({ maxSpaces }); + const mockConfig = createMockConfig({ enabled: true, maxSpaces }); const client = new SpacesClient(mockDebugLogger, mockConfig, mockCallWithRequestRepository, []); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/index.ts index ddfc694b1fd86..55b2a31d2ca80 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/index.ts @@ -22,6 +22,7 @@ import { getSlackConnectorType } from './slack'; import { getSwimlaneConnectorType } from './swimlane'; import { getTeamsConnectorType } from './teams'; import { getTinesConnectorType } from './tines'; +import { getTorqConnectorType } from './torq'; import { getWebhookConnectorType } from './webhook'; import { getXmattersConnectorType } from './xmatters'; @@ -55,5 +56,6 @@ export function registerConnectorTypes({ connectorTypeRegistry.register(getResilientConnectorType()); connectorTypeRegistry.register(getOpsgenieConnectorType()); connectorTypeRegistry.register(getTeamsConnectorType()); + connectorTypeRegistry.register(getTorqConnectorType()); connectorTypeRegistry.register(getTinesConnectorType()); } diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/torq/index.ts new file mode 100644 index 0000000000000..e7c2d205fe97b --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/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 { getActionType as getTorqConnectorType } from './torq'; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/logo.tsx new file mode 100644 index 0000000000000..3e73aefd98853 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/logo.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { LogoProps } from '../types'; + +const Logo = (props: LogoProps) => { + return ( + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { Logo as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq.test.tsx new file mode 100644 index 0000000000000..09bb5a210a333 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ActionTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; +import { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry'; +import { registerConnectorTypes } from '..'; +import { registrationServicesMock } from '../../mocks'; + +const ACTION_TYPE_ID = '.torq'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const connectorTypeRegistry = new TypeRegistry(); + registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock }); + const getResult = connectorTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + }); +}); + +describe('torq action params validation', () => { + test('action params validation succeeds when action params is valid', async () => { + const actionParams = { + body: '{"message": "{test}"}', + }; + + expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { body: [] }, + }); + }); + + test('action params validation succeeds when action params is valid - mustache', async () => { + const actionParams = { + body: '{"message": {{number}}}', + }; + + expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { body: [] }, + }); + }); + + test('params validation fails when body is empty', async () => { + const actionParams = { + body: '', + }; + + expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + body: ['Body is required.'], + }, + }); + }); + + test('params validation fails when body is not a valid JSON', async () => { + const actionParams = { + body: 'some text', + }; + + expect(await actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + body: ['Body must be a valid JSON.'], + }, + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq.tsx new file mode 100644 index 0000000000000..04eef6afa638b --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ActionTypeModel, + GenericValidationResult, +} from '@kbn/triggers-actions-ui-plugin/public/types'; +import { lazy } from 'react'; +import { TorqActionParams, TorqConfig, TorqSecrets } from '../types'; +import * as i18n from './translations'; + +const torqDefaultBody = `{ + "alert": {{alert}}, + "context": {{context}}, + "rule": {{rule}}, + "state": {{state}}, + "date": "{{date}}", + "kibana_base_url": "{{kibanaBaseUrl}}" +}`; + +function replaceReferencesWithNumbers(body: string) { + return body.replace(/\{\{[.\w]+\}\}/gm, '42'); +} + +export function getActionType(): ActionTypeModel { + const validateParams = async ( + actionParams: TorqActionParams + ): Promise> => { + const translations = await import('./translations'); + const errors = { + body: [] as string[], + }; + const validationResult = { errors }; + validationResult.errors = errors; + if (!actionParams.body?.length) { + errors.body.push(translations.BODY_REQUIRED); + } else { + try { + JSON.parse(replaceReferencesWithNumbers(actionParams.body || '')); + } catch (e) { + errors.body.push(translations.INVALID_JSON); + } + } + return validationResult; + }; + return { + id: '.torq', + iconClass: lazy(() => import('./logo')), + selectMessage: i18n.TORQ_SELECT_MESSAGE, + actionTypeTitle: i18n.TORQ_ACTION_TYPE_TITLE, + validateParams, + actionConnectorFields: lazy(() => import('./torq_connectors')), + actionParamsFields: lazy(() => import('./torq_params')), + defaultActionParams: { + body: torqDefaultBody, + }, + }; +} diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx new file mode 100644 index 0000000000000..d392d14d8b94e --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.test.tsx @@ -0,0 +1,180 @@ +/* + * 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 { act, render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../lib/test_utils'; +import TorqActionConnectorFields from './torq_connectors'; + +const EMPTY_FUNC = () => {}; + +describe('TorqActionConnectorFields renders', () => { + test('all connector fields are rendered', async () => { + const actionConnector = { + actionTypeId: '.torq', + name: 'torq', + config: { + webhookIntegrationUrl: 'https://hooks.torq.io/v1/webhooks/fjdkljfekdfjlsa', + }, + secrets: { + token: 'testtoken', + }, + isDeprecated: false, + }; + + const wrapper = mountWithIntl( + + + + ); + + await waitForComponentToUpdate(); + + expect(wrapper.find('[data-test-subj="torqUrlText"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="torqTokenInput"]').length > 0).toBeTruthy(); + }); + + describe('Validation', () => { + const onSubmit = jest.fn(); + const actionConnector = { + actionTypeId: '.torq', + name: 'torq', + config: { + webhookIntegrationUrl: 'https://hooks.torq.io/v1/webhooks/fjdksla', + }, + secrets: { + token: 'testtoken', + }, + isDeprecated: false, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('connector validation succeeds when connector config is valid', async () => { + const { getByTestId } = render( + + + + ); + + await act(async () => { + userEvent.click(getByTestId('form-test-provide-submit')); + }); + + expect(onSubmit).toBeCalledWith({ + data: { + actionTypeId: '.torq', + name: 'torq', + config: { + webhookIntegrationUrl: 'https://hooks.torq.io/v1/webhooks/fjdksla', + }, + secrets: { + token: 'testtoken', + }, + isDeprecated: false, + }, + isValid: true, + }); + }); + + it('connector validation fails when there is no token', async () => { + const connector = { + ...actionConnector, + secrets: { + token: '', + }, + }; + + const { getByTestId } = render( + + + + ); + + await act(async () => { + userEvent.click(getByTestId('form-test-provide-submit')); + }); + + expect(onSubmit).toBeCalledWith({ + data: {}, + isValid: false, + }); + }); + + it('connector validation fails when there is no webhook URL', async () => { + const connector = { + ...actionConnector, + config: { + webhookIntegrationUrl: '', + }, + }; + + const { getByTestId } = render( + + + + ); + + await act(async () => { + userEvent.click(getByTestId('form-test-provide-submit')); + }); + + expect(onSubmit).toBeCalledWith({ + data: {}, + isValid: false, + }); + }); + + it('connector validation fails if the URL is not of a Torq webhook', async () => { + const connector = { + ...actionConnector, + config: { + webhookIntegrationUrl: 'https://test.com', + }, + }; + + const { getByTestId } = render( + + + + ); + + await act(async () => { + userEvent.click(getByTestId('form-test-provide-submit')); + }); + + expect(onSubmit).toBeCalledWith({ + data: {}, + isValid: false, + }); + }); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx new file mode 100644 index 0000000000000..84aaf1a8738ce --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_connectors.tsx @@ -0,0 +1,97 @@ +/* + * 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 { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { Field } from '@kbn/es-ui-shared-plugin/static/forms/components'; +import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; +import { ERROR_CODE } from '@kbn/es-ui-shared-plugin/static/forms/helpers/field_validators/types'; +import { + UseField, + ValidationError, + ValidationFunc, +} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; +import { isUrl } from '@kbn/es-ui-shared-plugin/static/validators/string'; +import { ActionConnectorFieldsProps, PasswordField } from '@kbn/triggers-actions-ui-plugin/public'; +import React from 'react'; +import * as i18n from './translations'; + +const { urlField } = fieldValidators; + +const Callout: React.FC<{ title: string; dataTestSubj: string }> = ({ title, dataTestSubj }) => { + return ( + <> + + + + + ); +}; + +const torqWebhookEndpoint = + (message: string) => + (...args: Parameters): ReturnType> => { + const [{ value }] = args as Array<{ value: string }>; + const error: ValidationError = { + code: 'ERR_FIELD_FORMAT', + formatType: 'URL', + message, + }; + if (!isUrl(value)) return error; + const hostname = new URL(value).hostname; + return hostname === 'hooks.torq.io' ? undefined : error; + }; + +const TorqActionConnectorFields: React.FunctionComponent = ({ + readOnly, +}) => { + return ( + <> + + + + + + + + + + + + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TorqActionConnectorFields as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_params.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_params.test.tsx new file mode 100644 index 0000000000000..43e1d1248eec6 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_params.test.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 { mountWithIntl } from '@kbn/test-jest-helpers'; +import { MockCodeEditor } from '@kbn/triggers-actions-ui-plugin/public/application/code_editor.mock'; +import React from 'react'; +import TorqParamsFields from './torq_params'; + +const kibanaReactPath = '../../../../../../src/plugins/kibana_react/public'; + +jest.mock(kibanaReactPath, () => { + const original = jest.requireActual(kibanaReactPath); + return { + ...original, + CodeEditor: (props: any) => { + return ; + }, + }; +}); + +describe('TorqParamsFields renders', () => { + test('all params fields is rendered', () => { + const actionParams = { + body: 'test message', + }; + + const wrapper = mountWithIntl( + {}} + index={0} + messageVariables={[ + { + name: 'myVar', + description: 'My variable description', + useWithTripleBracesInTemplates: true, + }, + ]} + /> + ); + expect(wrapper.find('[data-test-subj="bodyJsonEditor"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="bodyJsonEditor"]').first().prop('value')).toStrictEqual( + 'test message' + ); + expect(wrapper.find('[data-test-subj="bodyAddVariableButton"]').length > 0).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_params.tsx new file mode 100644 index 0000000000000..e6cb9880c3ef2 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/torq_params.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 { + ActionParamsProps, + JsonEditorWithMessageVariables, +} from '@kbn/triggers-actions-ui-plugin/public'; +import React from 'react'; +import { TorqActionParams } from '../types'; +import * as i18n from './translations'; + +const TorqParamsFields: React.FunctionComponent> = ({ + actionParams, + editAction, + index, + messageVariables, + errors, +}) => { + const { body } = actionParams; + return ( + { + editAction('body', json, index); + }} + onBlur={() => { + if (!body) { + editAction('body', '', index); + } + }} + euiCodeEditorProps={{ + options: { + renderValidationDecorations: body && errors?.body?.length ? 'on' : 'off', + lineNumbers: 'on', + fontSize: 14, + minimap: { + enabled: false, + }, + scrollBeyondLastLine: false, + folding: true, + wordWrap: 'on', + wrappingIndent: 'indent', + automaticLayout: true, + }, + }} + /> + ); +}; + +// eslint-disable-next-line import/no-default-export +export { TorqParamsFields as default }; diff --git a/x-pack/plugins/stack_connectors/public/connector_types/torq/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/torq/translations.ts new file mode 100644 index 0000000000000..6985e6dd01ce3 --- /dev/null +++ b/x-pack/plugins/stack_connectors/public/connector_types/torq/translations.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const URL_LABEL = i18n.translate('xpack.stackConnectors.torqAction.urlTextFieldLabel', { + defaultMessage: 'Torq endpoint URL', +}); + +export const URL_INVALID = i18n.translate( + 'xpack.stackConnectors.torqAction.error.invalidUrlTextField', + { + defaultMessage: 'URL is invalid.', + } +); + +export const BODY_FIELD_LABEL = i18n.translate('xpack.stackConnectors.torqAction.bodyFieldLabel', { + defaultMessage: 'Body', +}); + +export const BODY_FIELD_ARIA_LABEL = i18n.translate( + 'xpack.stackConnectors.torqAction.bodyCodeEditorAriaLabel', + { + defaultMessage: 'Code editor', + } +); + +export const URL_NOT_TORQ_WEBHOOK = i18n.translate( + 'xpack.stackConnectors.torqAction.error.urlIsNotTorqWebhook', + { + defaultMessage: 'URL is not a Torq integration endpoint.', + } +); + +export const TORQ_TOKEN_LABEL = i18n.translate('xpack.stackConnectors.torqAction.token', { + defaultMessage: 'Torq integration token', +}); + +export const BODY_REQUIRED = i18n.translate('xpack.stackConnectors.error.requiredWebhookBodyText', { + defaultMessage: 'Body is required.', +}); + +export const INVALID_JSON = i18n.translate('xpack.stackConnectors.error.requireValidJSONBody', { + defaultMessage: 'Body must be a valid JSON.', +}); + +export const TORQ_SELECT_MESSAGE = i18n.translate( + 'xpack.stackConnectors.torqAction.selectMessageText', + { + defaultMessage: 'Trigger a Torq workflow.', + } +); + +export const TORQ_ACTION_TYPE_TITLE = i18n.translate( + 'xpack.stackConnectors.torqAction.actionTypeTitle', + { + defaultMessage: 'Alert data', + } +); + +export const TORQ_TOKEN_HELP_TEXT = i18n.translate( + 'xpack.stackConnectors.torqAction.tokenHelpText', + { + defaultMessage: + 'Enter the webhook authentication header secret generated when you created the Elastic Security integration.', + } +); + +export const URL_HELP_TEXT = i18n.translate('xpack.stackConnectors.torqAction.urlHelpText', { + defaultMessage: + 'Enter the endpoint URL generated when you created the Elastic Security integration on Torq.', +}); + +export const HOW_TO_TEXT = i18n.translate( + 'xpack.stackConnectors.torqActionConnectorFields.calloutTitle', + { + defaultMessage: + 'Create an Elastic Security integration on Torq, and then come back and paste the endpoint URL and token generated for your integration.', + } +); diff --git a/x-pack/plugins/stack_connectors/public/connector_types/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/types.ts index 693681333ea1c..72319df375e1f 100644 --- a/x-pack/plugins/stack_connectors/public/connector_types/types.ts +++ b/x-pack/plugins/stack_connectors/public/connector_types/types.ts @@ -72,6 +72,10 @@ export interface WebhookActionParams { body?: string; } +export interface TorqActionParams { + body?: string; +} + export interface EmailConfig { from: string; host: string; @@ -132,6 +136,16 @@ export interface WebhookSecrets { export type WebhookActionConnector = UserConfiguredActionConnector; +export interface TorqConfig { + url: string; +} + +export interface TorqSecrets { + token: string; +} + +export type TorqActionConnector = UserConfiguredActionConnector; + export enum XmattersSeverityOptions { CRITICAL = 'critical', HIGH = 'high', diff --git a/x-pack/plugins/stack_connectors/server/connector_types/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/index.test.ts index ead127d7dd521..c2aaf7c7ea5d8 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/index.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.test.ts @@ -18,6 +18,7 @@ const ACTION_TYPE_IDS = [ '.teams', '.webhook', '.xmatters', + '.torq', ]; const mockedActions = actionsMock.createSetup(); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/index.ts index 0c0e6d123a792..648695bc7cbbd 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/index.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/index.ts @@ -14,6 +14,7 @@ import { getServiceNowITSMConnectorType } from './servicenow_itsm'; import { getServiceNowSIRConnectorType } from './servicenow_sir'; import { getServiceNowITOMConnectorType } from './servicenow_itom'; import { getTinesConnectorType } from './tines'; +import { getActionType as getTorqConnectorType } from './torq'; import { getConnectorType as getEmailConnectorType } from './email'; import { getConnectorType as getIndexConnectorType } from './es_index'; import { getConnectorType as getPagerDutyConnectorType } from './pagerduty'; @@ -89,6 +90,7 @@ export function registerConnectorTypes({ actions.registerType(getJiraConnectorType()); actions.registerType(getResilientConnectorType()); actions.registerType(getTeamsConnectorType()); + actions.registerType(getTorqConnectorType()); actions.registerSubActionConnectorType(getOpsgenieConnectorType()); actions.registerSubActionConnectorType(getTinesConnectorType()); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts new file mode 100644 index 0000000000000..e970e1f678bde --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.test.ts @@ -0,0 +1,235 @@ +/* + * 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 { Logger } from '@kbn/core/server'; + +import axios from 'axios'; +import { ActionTypeConfigType, getActionType, TorqActionType } from '.'; + +import * as utils from '@kbn/actions-plugin/server/lib/axios_utils'; +import { validateConfig, validateParams, validateSecrets } from '@kbn/actions-plugin/server/lib'; +import { actionsMock } from '@kbn/actions-plugin/server/mocks'; +import { Services } from '@kbn/actions-plugin/server/types'; +import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock'; +import { loggerMock } from '@kbn/logging-mocks'; +import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config'; + +jest.mock('axios'); +jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => { + const originalUtils = jest.requireActual('@kbn/actions-plugin/server/lib/axios_utils'); + return { + ...originalUtils, + request: jest.fn(), + patch: jest.fn(), + }; +}); + +const requestMock = utils.request as jest.Mock; + +axios.create = jest.fn(() => axios); + +const services: Services = actionsMock.createServices(); + +let actionType: TorqActionType; +const mockedLogger: jest.Mocked = loggerMock.create(); +let configurationUtilities: jest.Mocked; + +beforeAll(() => { + actionType = getActionType(); + configurationUtilities = actionsConfigMock.create(); +}); + +describe('actionType', () => { + test('exposes the action as `torq` on its Id and Name', () => { + expect(actionType.id).toEqual('.torq'); + expect(actionType.name).toEqual('Torq'); + }); +}); + +describe('secrets validation', () => { + test('succeeds when secrets is valid', () => { + const secrets: Record = { + token: 'jfi2fji3ofeaiw34if', + }; + expect(validateSecrets(actionType, secrets, { configurationUtilities })).toEqual(secrets); + }); + + test('fails when secret token is not provided', () => { + expect(() => { + validateSecrets(actionType, {}, { configurationUtilities }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [token]: expected value of type [string] but got [undefined]"` + ); + }); +}); + +describe('config validation', () => { + const defaultValues: Record = {}; + + test('config validation passes with an appropriate endpoint', () => { + const config: Record = { + webhookIntegrationUrl: 'https://hooks.torq.io/v1/test', + }; + expect(validateConfig(actionType, config, { configurationUtilities })).toEqual({ + ...defaultValues, + ...config, + }); + }); + + const errorCases: Array<{ name: string; url: string; errorMsg: string }> = [ + { + name: 'invalid URL leads to error', + url: 'iamnotavalidurl', + errorMsg: `"error validating action type config: error configuring send to Torq action: unable to parse url: TypeError: Invalid URL: iamnotavalidurl"`, + }, + { + name: 'incomplete URL leads to error', + url: 'example.com/do-something', + errorMsg: `"error validating action type config: error configuring send to Torq action: unable to parse url: TypeError: Invalid URL: example.com/do-something"`, + }, + { + name: 'fails when URL is not a Torq webhook endpoint', + url: 'http://mylisteningserver:9200/endpoint', + errorMsg: `"error validating action type config: error configuring send to Torq action: url must begin with https://hooks.torq.io"`, + }, + ]; + errorCases.forEach(({ name, url, errorMsg }) => { + test(name, () => { + const config: Record = { + webhookIntegrationUrl: url, + }; + expect(() => { + validateConfig(actionType, config, { configurationUtilities }); + }).toThrowErrorMatchingInlineSnapshot(errorMsg); + }); + }); + + test("config validation returns an error if the specified URL isn't added to allowedHosts", () => { + actionType = getActionType(); + + const configUtils = { + ...actionsConfigMock.create(), + ensureUriAllowed: (_: string) => { + throw new Error(`target url is not present in allowedHosts`); + }, + }; + + // any for testing + const config: Record = { + webhookIntegrationUrl: 'http://mylisteningserver.com:9200/endpoint', + }; + + expect(() => { + validateConfig(actionType, config, { configurationUtilities: configUtils }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: error configuring send to Torq action: target url is not present in allowedHosts"` + ); + }); +}); + +describe('params validation', () => { + test('params validation passes when a valid body is provided', () => { + const params: Record = { + body: '{"message": "Hello"}', + }; + expect(validateParams(actionType, params, { configurationUtilities })).toEqual({ + ...params, + }); + }); +}); + +describe('execute Torq action', () => { + beforeAll(() => { + requestMock.mockReset(); + actionType = getActionType(); + }); + + beforeEach(() => { + requestMock.mockReset(); + requestMock.mockResolvedValue({ + status: 200, + statusText: '', + data: '', + headers: [], + config: {}, + }); + }); + + test('execute with token happy flow', async () => { + const config: ActionTypeConfigType = { + webhookIntegrationUrl: 'https://hooks.torq.io/v1/test', + }; + await actionType.executor({ + actionId: 'some-id', + services, + config, + secrets: { token: '1234' }, + params: { body: '{"msg": "some data"}' }, + configurationUtilities, + logger: mockedLogger, + }); + + delete requestMock.mock.calls[0][0].configurationUtilities; + expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "axios": [MockFunction], + "data": Object { + "msg": "some data", + }, + "headers": Object { + "Content-Type": "application/json", + "X-Torq-Token": "1234", + }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from Torq action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "isLevelEnabled": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "url": "https://hooks.torq.io/v1/test", + "validateStatus": [Function], + } + `); + }); + + test('renders parameter templates as expected', async () => { + const templatedObject = `{"material": "rubber", "kind": "band"}`; + + expect(actionType.renderParameterTemplates).toBeTruthy(); + const paramsWithTemplates = { + body: '{"x": {{obj}}, "y": "{{scalar}}", "z": "{{scalar_with_json_chars}}"}', + }; + const variables = { + obj: templatedObject, + scalar: '1970', + scalar_with_json_chars: 'noinjection", "here": "', + }; + const params = actionType.renderParameterTemplates!(paramsWithTemplates, variables); + expect(params.body).toBe( + `{"x": ${templatedObject}, "y": "${variables.scalar}", "z": "${variables.scalar_with_json_chars}"}` + ); + }); +}); diff --git a/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts new file mode 100644 index 0000000000000..860e87397eb44 --- /dev/null +++ b/x-pack/plugins/stack_connectors/server/connector_types/torq/index.ts @@ -0,0 +1,391 @@ +/* + * 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 { curry } from 'lodash'; +import axios, { AxiosError, AxiosResponse } from 'axios'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { map, getOrElse } from 'fp-ts/lib/Option'; +import { Logger } from '@kbn/core/server'; +import { ActionType, ActionTypeExecutorOptions } from '@kbn/actions-plugin/server'; +import { + AlertingConnectorFeatureId, + UptimeConnectorFeatureId, + SecurityConnectorFeatureId, + ActionTypeExecutorResult, +} from '@kbn/actions-plugin/common'; +import { renderMustacheObject } from '@kbn/actions-plugin/server/lib/mustache_renderer'; +import { request } from '@kbn/actions-plugin/server/lib/axios_utils'; +import { ValidatorServices } from '@kbn/actions-plugin/server/types'; +import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header'; +import { promiseResult, isOk, Result } from '../lib/result_type'; + +export type TorqActionType = ActionType< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType, + unknown +>; +export type TorqActionTypeExecutorOptions = ActionTypeExecutorOptions< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType +>; + +const configSchemaProps = { + webhookIntegrationUrl: schema.string(), +}; +const ConfigSchema = schema.object(configSchemaProps); +export type ActionTypeConfigType = TypeOf; + +// secrets definition +export type ActionTypeSecretsType = TypeOf; +const secretSchemaProps = { + token: schema.string(), +}; +const SecretsSchema = schema.object(secretSchemaProps); + +// params definition +export type ActionParamsType = TypeOf; +const ParamsSchema = schema.object({ + body: schema.string(), +}); + +export const ActionTypeId = '.torq'; +// action type definition +export function getActionType(): TorqActionType { + return { + id: ActionTypeId, + minimumLicenseRequired: 'gold', + name: i18n.translate('xpack.stackConnectors.torqTitle', { + defaultMessage: 'Torq', + }), + supportedFeatureIds: [ + AlertingConnectorFeatureId, + UptimeConnectorFeatureId, + SecurityConnectorFeatureId, + ], + validate: { + config: { + schema: schema.object(configSchemaProps), + customValidator: validateActionTypeConfig, + }, + secrets: { + schema: SecretsSchema, + }, + params: { + schema: ParamsSchema, + }, + }, + renderParameterTemplates, + executor: curry(executor)(), + }; +} + +function renderParameterTemplates( + params: ActionParamsType, + variables: Record +): ActionParamsType { + if (!params.body) return params; + return renderMustacheObject(params, variables); +} + +function validateActionTypeConfig( + configObject: ActionTypeConfigType, + validatorServices: ValidatorServices +) { + const configuredUrl = configObject.webhookIntegrationUrl; + let configureUrlObj: URL; + try { + configureUrlObj = new URL(configuredUrl); + } catch (err) { + throw new Error( + i18n.translate('xpack.stackConnectors.torq.torqConfigurationErrorNoHostname', { + defaultMessage: 'error configuring send to Torq action: unable to parse url: {err}', + values: { + err, + }, + }) + ); + } + + try { + validatorServices.configurationUtilities.ensureUriAllowed(configuredUrl); + } catch (allowListError) { + throw new Error( + i18n.translate('xpack.stackConnectors.torq.torqConfigurationError', { + defaultMessage: 'error configuring send to Torq action: {message}', + values: { + message: allowListError.message, + }, + }) + ); + } + + if (configureUrlObj.hostname !== 'hooks.torq.io' && configureUrlObj.hostname !== 'localhost') { + throw new Error( + i18n.translate('xpack.stackConnectors.torq.torqConfigurationErrorInvalidHostname', { + defaultMessage: + 'error configuring send to Torq action: url must begin with https://hooks.torq.io', + }) + ); + } +} + +// action executor +export async function executor( + execOptions: TorqActionTypeExecutorOptions +): Promise> { + const actionId = execOptions.actionId; + const { webhookIntegrationUrl } = execOptions.config; + const { body: data } = execOptions.params; + const configurationUtilities = execOptions.configurationUtilities; + + const secrets: ActionTypeSecretsType = execOptions.secrets; + const token = secrets.token; + + let body; + try { + body = JSON.parse(data || 'null'); + } catch (err) { + return errorInvalidBody(actionId, execOptions.logger, err); + } + + const axiosInstance = axios.create(); + const result: Result> = await promiseResult( + request({ + axios: axiosInstance, + url: webhookIntegrationUrl, + method: 'post', + headers: { + 'X-Torq-Token': token || '', + 'Content-Type': 'application/json', + }, + data: body, + configurationUtilities, + logger: execOptions.logger, + validateStatus: (status: number) => status >= 200 && status < 300, + }) + ); + + if (isOk(result)) { + const { + value: { status, statusText }, + } = result; + execOptions.logger.debug( + `response from Torq action "${actionId}": [HTTP ${status}] ${statusText}` + ); + return successResult(actionId, data); + } + const { error } = result; + return handleExecutionError(error, execOptions.logger, actionId); +} + +async function handleExecutionError( + error: AxiosError<{ message: string }>, + logger: Logger, + actionId: string +): Promise> { + if (error.response) { + const { + status, + statusText, + headers: responseHeaders, + data: { message: responseMessage }, + } = error.response; + const responseMessageAsSuffix = responseMessage ? `: ${responseMessage}` : ''; + const message = `[${status}] ${statusText}${responseMessageAsSuffix}`; + logger.error(`error on ${actionId} Torq event: ${message}`); + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + // special handling for 5xx + if (status >= 500) { + return retryResult(actionId, message); + } + + // special handling for rate limiting + if (status === 429) { + return pipe( + getRetryAfterIntervalFromHeaders(responseHeaders), + map((retry) => retryResultSeconds(actionId, message, retry)), + getOrElse(() => retryResult(actionId, message)) + ); + } + + if (status === 405) { + return errorResultInvalidMethod(actionId, message); + } + + if (status === 401) { + return errorResultUnauthorised(actionId, message); + } + + if (status === 404) { + return errorNotFound(actionId, message); + } + + return errorResultInvalid(actionId, message); + } else if (error.code) { + const message = `[${error.code}] ${error.message}`; + logger.error(`error on ${actionId} Torq event: ${message}`); + return errorResultRequestFailed(actionId, message); + } else if (error.isAxiosError) { + const message = `${error.message}`; + logger.error(`error on ${actionId} Torq event: ${message}`); + return errorResultRequestFailed(actionId, message); + } + logger.error(`error on ${actionId} Torq action: unexpected error`); + return errorResultUnexpectedError(actionId); +} + +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { + return { status: 'ok', data, actionId }; +} + +function errorInvalidBody( + actionId: string, + logger: Logger, + err: Error +): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.invalidBodyErrorMessage', { + defaultMessage: 'error triggering Torq workflow, invalid body', + }); + logger.error(`error on ${actionId} Torq event: ${errMessage}: ${err.message}`); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage: err.message, + }; +} + +function errorResultInvalid( + actionId: string, + serviceMessage: string +): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.invalidResponseErrorMessage', { + defaultMessage: 'error triggering Torq workflow, invalid response', + }); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage, + }; +} + +function errorNotFound(actionId: string, serviceMessage: string): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.notFoundErrorMessage', { + defaultMessage: 'error triggering Torq workflow, make sure the webhook URL is valid', + }); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage, + }; +} + +function errorResultRequestFailed( + actionId: string, + serviceMessage: string +): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.requestFailedErrorMessage', { + defaultMessage: 'error triggering Torq workflow, request failed', + }); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage, + }; +} + +function errorResultInvalidMethod( + actionId: string, + serviceMessage: string +): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.invalidMethodErrorMessage', { + defaultMessage: 'error triggering Torq workflow, method is not supported', + }); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage, + }; +} + +function errorResultUnauthorised( + actionId: string, + serviceMessage: string +): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.unauthorisedErrorMessage', { + defaultMessage: 'error triggering Torq workflow, unauthorised', + }); + return { + status: 'error', + message: errMessage, + actionId, + serviceMessage, + }; +} + +function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { + const errMessage = i18n.translate('xpack.stackConnectors.torq.unreachableErrorMessage', { + defaultMessage: 'error triggering Torq workflow, unexpected error', + }); + return { + status: 'error', + message: errMessage, + actionId, + }; +} + +function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { + const errMessage = i18n.translate( + 'xpack.stackConnectors.torq.invalidResponseRetryLaterErrorMessage', + { + defaultMessage: 'error triggering Torq workflow, retry later', + } + ); + return { + status: 'error', + message: errMessage, + retry: true, + actionId, + serviceMessage, + }; +} + +function retryResultSeconds( + actionId: string, + serviceMessage: string, + + retryAfter: number +): ActionTypeExecutorResult { + const retryEpoch = Date.now() + retryAfter * 1000; + const retry = new Date(retryEpoch); + const retryString = retry.toISOString(); + const errMessage = i18n.translate( + 'xpack.stackConnectors.torq.invalidResponseRetryDateErrorMessage', + { + defaultMessage: 'error triggering Torq workflow, retry at {retryString}', + values: { + retryString, + }, + } + ); + return { + status: 'error', + message: errMessage, + retry, + actionId, + serviceMessage, + }; +} diff --git a/x-pack/plugins/stack_connectors/server/plugin.test.ts b/x-pack/plugins/stack_connectors/server/plugin.test.ts index b28899ecf0865..bfc2bb9fd4197 100644 --- a/x-pack/plugins/stack_connectors/server/plugin.test.ts +++ b/x-pack/plugins/stack_connectors/server/plugin.test.ts @@ -25,7 +25,7 @@ describe('Stack Connectors Plugin', () => { it('should register built in connector types', () => { const actionsSetup = actionsMock.createSetup(); plugin.setup(coreSetup, { actions: actionsSetup }); - expect(actionsSetup.registerType).toHaveBeenCalledTimes(15); + expect(actionsSetup.registerType).toHaveBeenCalledTimes(16); expect(actionsSetup.registerType).toHaveBeenNthCalledWith( 1, expect.objectContaining({ diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts index bc6c70f60a7fe..57737c2cef192 100644 --- a/x-pack/plugins/synthetics/common/constants/ui.ts +++ b/x-pack/plugins/synthetics/common/constants/ui.ts @@ -7,6 +7,8 @@ export const MONITOR_ROUTE = '/monitor/:monitorId?'; +export const MONITOR_NOT_FOUND_ROUTE = '/monitor-not-found/:monitorId'; + export const MONITOR_HISTORY_ROUTE = '/monitor/:monitorId/history'; export const MONITOR_ERRORS_ROUTE = '/monitor/:monitorId/errors'; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/index.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/index.ts index 7cd93a3e93da0..b3a06e8006222 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/index.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/index.ts @@ -12,5 +12,6 @@ export * from './monitor_meta_data'; export * from './monitor_types'; export * from './monitor_types_project'; export * from './locations'; +export * from './synthetics_private_locations'; export * from './synthetics_overview_status'; export * from './synthetics_params'; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/state.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/state.ts index d997c554eef0e..a167e869dcb9a 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/state.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/state.ts @@ -16,7 +16,9 @@ export const FetchMonitorManagementListQueryArgsCodec = t.partial({ searchFields: t.array(t.string), tags: t.array(t.string), locations: t.array(t.string), - monitorType: t.array(t.string), + monitorTypes: t.array(t.string), + projects: t.array(t.string), + schedules: t.array(t.string), }); export type FetchMonitorManagementListQueryArgs = t.TypeOf< @@ -28,7 +30,9 @@ export const FetchMonitorOverviewQueryArgsCodec = t.partial({ searchFields: t.array(t.string), tags: t.array(t.string), locations: t.array(t.string), - monitorType: t.array(t.string), + projects: t.array(t.string), + schedules: t.array(t.string), + monitorTypes: t.array(t.string), sortField: t.string, sortOrder: t.string, }); diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts index a942316202ebb..a519036240b50 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/synthetics_overview_status.ts @@ -27,6 +27,7 @@ export const OverviewStatusCodec = t.interface({ disabledCount: t.number, upConfigs: t.record(t.string, OverviewStatusMetaDataCodec), downConfigs: t.record(t.string, OverviewStatusMetaDataCodec), + allIds: t.array(t.string), enabledIds: t.array(t.string), }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts index ddbdd8091bce0..5b8f6670c2f21 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/getting_started.journey.ts @@ -42,6 +42,7 @@ journey(`Getting Started Page`, async ({ page, params }: { page: Page; params: a }); step('shows validation error on submit', async () => { + await page.locator('.euiSideNavItem').locator('text=Synthetics').click(); await page.click('text=Create monitor'); expect(await page.isVisible('text=URL is required')).toBeTruthy(); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts index 15dd3c2b87247..bf4ecd526e911 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/index.ts @@ -23,3 +23,4 @@ export * from './data_retention.journey'; export * from './monitor_details_page/monitor_summary.journey'; export * from './test_run_details.journey'; export * from './step_details.journey'; +export * from './project_monitor_read_only.journey'; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/management_list.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/management_list.journey.ts index ad220cbc20ded..f9751d37c6a41 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/management_list.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/management_list.journey.ts @@ -36,7 +36,10 @@ journey(`MonitorManagementList`, async ({ page, params }) => { await addTestMonitor(params.kibanaUrl, testMonitor1); await addTestMonitor(params.kibanaUrl, testMonitor2); - await addTestMonitor(params.kibanaUrl, testMonitor3); + await addTestMonitor(params.kibanaUrl, testMonitor3, { + type: 'browser', + schedule: { unit: 'm', number: '5' }, + }); }); after(async () => { @@ -64,17 +67,17 @@ journey(`MonitorManagementList`, async ({ page, params }) => { }); step( - 'Click [aria-label="Use up and down arrows to move focus over options. Enter to select. Escape to collapse options."] >> text=browser', + 'Click [aria-label="Use up and down arrows to move focus over options. Enter to select. Escape to collapse options."] >> text="Journey / Page"', async () => { await page.click( - '[aria-label="Use up and down arrows to move focus over options. Enter to select. Escape to collapse options."] >> text=browser' + '[aria-label="Use up and down arrows to move focus over options. Enter to select. Escape to collapse options."] >> text="Journey / Page"' ); await page.click('[aria-label="Apply the selected filters for Type"]'); - expect(page.url()).toBe(`${pageBaseUrl}?monitorType=%5B%22browser%22%5D`); + expect(page.url()).toBe(`${pageBaseUrl}?monitorTypes=%5B%22browser%22%5D`); await page.click('[placeholder="Search by name, url, host, tag, project or location"]'); await Promise.all([ page.waitForNavigation({ - url: `${pageBaseUrl}?monitorType=%5B%22browser%22%5D&query=3`, + url: `${pageBaseUrl}?monitorTypes=%5B%22browser%22%5D&query=3`, }), page.fill('[placeholder="Search by name, url, host, tag, project or location"]', '3'), ]); @@ -99,4 +102,22 @@ journey(`MonitorManagementList`, async ({ page, params }) => { await expect(statSummaryPanel.locator('text=3').count()).resolves.toEqual(1); // Configurations await expect(statSummaryPanel.locator('text=0').count()).resolves.toEqual(1); // Disabled }); + + step('Filter by Frequency', async () => { + const frequencyFilter = page.locator('.euiFilterButton__textShift', { hasText: 'Frequency' }); + const fiveMinuteScheduleOption = page.getByText('Every 5 minutes').first(); + + await frequencyFilter.click(); + await fiveMinuteScheduleOption.click(); + await page.getByText('Apply').click(); + + // There should be only 1 monitor with schedule 5 minutes + await page.waitForSelector('text=1-1'); + + // Clear the filter + await frequencyFilter.click(); + await fiveMinuteScheduleOption.click(); + await page.getByText('Apply').click(); + await page.waitForSelector('text=1-3'); + }); }); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/project_monitor_read_only.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/project_monitor_read_only.journey.ts new file mode 100644 index 0000000000000..5daf18f855903 --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/project_monitor_read_only.journey.ts @@ -0,0 +1,110 @@ +/* + * 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 { after, before, expect, journey, step } from '@elastic/synthetics'; +import { recordVideo } from '@kbn/observability-plugin/e2e/record_video'; +import { cleanTestMonitors, enableMonitorManagedViaApi } from './services/add_monitor'; +import { getMonitor } from './services/get_monitor'; +import { addTestMonitorProject } from './services/add_monitor_project'; +import { syntheticsAppPageProvider } from '../../page_objects/synthetics/synthetics_app'; + +journey('Project Monitor Read-only', async ({ page, params }) => { + recordVideo(page); + + const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl }); + let originalMonitorConfiguration: Record | undefined; + + let monitorId: string; + const monitorName = 'test-project-monitor'; + + before(async () => { + await enableMonitorManagedViaApi(params.kibanaUrl); + await cleanTestMonitors(params); + + await addTestMonitorProject(params.kibanaUrl, monitorName); + + await syntheticsApp.waitForLoadingToFinish(); + }); + + step('Go to monitor-management', async () => { + await syntheticsApp.navigateToMonitorManagement(); + }); + + step('login to Kibana', async () => { + await syntheticsApp.loginToKibana(); + const invalid = await page.locator(`text=Username or password is incorrect. Please try again.`); + expect(await invalid.isVisible()).toBeFalsy(); + }); + + step('Confirm monitor is added', async () => { + await page.waitForSelector(`text=${monitorName}`); + }); + + step('Navigate to edit monitor', async () => { + await syntheticsApp.navigateToEditMonitor(monitorName); + }); + + step('Confirm configuration is read-only', async () => { + await page.waitForSelector('text=read-only'); + monitorId = new URL(await page.url()).pathname.split('/').at(-1) || ''; + originalMonitorConfiguration = await getMonitor(params.kibanaUrl, monitorId); + }); + + step('Monitor configuration is unchanged when saved', async () => { + await syntheticsApp.confirmAndSave(true); + const newConfiguration = await getMonitor(params.kibanaUrl, monitorId); + + // hash is always reset to empty string when monitor is edited + // this ensures that when the monitor is pushed again, the monitor + // config in the process takes precedence + expect(newConfiguration).toEqual({ ...originalMonitorConfiguration, hash: '', revision: 2 }); + }); + + step('Navigate to edit monitor', async () => { + await syntheticsApp.navigateToEditMonitor(monitorName); + }); + + step('monitor can be enabled or disabled', async () => { + await page.click('[data-test-subj="syntheticsEnableSwitch"]'); + + await syntheticsApp.confirmAndSave(true); + const newConfiguration = await getMonitor(params.kibanaUrl, monitorId); + + // hash is always reset to empty string when monitor is edited + // this ensures that when the monitor is pushed again, the monitor + // config in the process takes precedence + expect(newConfiguration).toEqual({ + ...originalMonitorConfiguration, + hash: '', + revision: 3, + enabled: !originalMonitorConfiguration?.enabled, + }); + }); + + step('Monitor can be repushed and overwrite any changes', async () => { + await addTestMonitorProject(params.kibanaUrl, monitorName); + const repushedConfiguration = await getMonitor(params.kibanaUrl, monitorId); + expect(repushedConfiguration).toEqual({ + ...originalMonitorConfiguration, + revision: 4, + }); + }); + + step('Navigate to edit monitor', async () => { + await syntheticsApp.navigateToEditMonitor(monitorName); + }); + + step('Monitor can be deleted', async () => { + await page.click('text="Delete monitor"'); + await page.click('[data-test-subj="confirmModalConfirmButton"]'); + await page.waitForSelector(`text='Deleted "${monitorName}"'`); + }); + + after(async () => { + await cleanTestMonitors(params); + }); +}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor_project.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor_project.ts new file mode 100644 index 0000000000000..ef11a9d27d449 --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/add_monitor_project.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; + +export const addTestMonitorProject = async ( + kibanaUrl: string, + name: string, + projectName: string = 'test-project', + config?: Record +) => { + const testData = { + ...testProjectMonitorBrowser(name, config), + }; + try { + return await axios.put( + kibanaUrl + `/api/synthetics/project/${projectName}/monitors/_bulk_update`, + testData, + { + auth: { username: 'elastic', password: 'changeme' }, + headers: { 'kbn-xsrf': 'true' }, + } + ); + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + } +}; + +const testProjectMonitorBrowser = (name: string, config?: Record) => ({ + monitors: [ + { + throttling: { + download: 5, + upload: 3, + latency: 20, + }, + schedule: 10, + locations: ['us_central'], + params: {}, + playwrightOptions: { + headless: true, + chromiumSandbox: false, + }, + id: 'check-if-title-is-present', + tags: [], + content: + 'UEsDBBQACAAIAON5qVQAAAAAAAAAAAAAAAAfAAAAZXhhbXBsZXMvdG9kb3MvYmFzaWMuam91cm5leS50c22Q0WrDMAxF3/sVF7MHB0LMXlc6RvcN+wDPVWNviW0sdUsp/fe5SSiD7UFCWFfHujIGlpnkybwxFTZfoY/E3hsaLEtwhs9RPNWKDU12zAOxkXRIbN4tB9d9pFOJdO6EN2HMqQguWN9asFBuQVMmJ7jiWNII9fIXrbabdUYr58l9IhwhQQZCYORCTFFUC31Btj21NRc7Mq4Nds+4bDD/pNVgT9F52Jyr2Fa+g75LAPttg8yErk+S9ELpTmVotlVwnfNCuh2lepl3+JflUmSBJ3uggt1v9INW/lHNLKze9dJe1J3QJK8pSvWkm6aTtCet5puq+x63+AFQSwcIAPQ3VfcAAACcAQAAUEsBAi0DFAAIAAgA43mpVAD0N1X3AAAAnAEAAB8AAAAAAAAAAAAgAKSBAAAAAGV4YW1wbGVzL3RvZG9zL2Jhc2ljLmpvdXJuZXkudHNQSwUGAAAAAAEAAQBNAAAARAEAAAAA', + filter: { + match: 'check if title is present', + }, + hash: 'ekrjelkjrelkjre', + name, + ...config, + }, + ], +}); diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/get_monitor.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/get_monitor.ts new file mode 100644 index 0000000000000..20bd20ad10147 --- /dev/null +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/services/get_monitor.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; + +export const getMonitor = async (kibanaUrl: string, monitorId: string) => { + try { + const response = await axios.get(kibanaUrl + `/internal/uptime/service/monitors/${monitorId}`, { + auth: { username: 'elastic', password: 'changeme' }, + headers: { 'kbn-xsrf': 'true' }, + }); + return response.data.attributes; + } catch (e) { + // eslint-disable-next-line no-console + console.log(e); + } +}; diff --git a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts index 67ee9f278b9bf..6c61272b24b91 100644 --- a/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts +++ b/x-pack/plugins/synthetics/e2e/journeys/synthetics/test_now_mode.journey.ts @@ -137,7 +137,7 @@ journey(`TestNowMode`, async ({ page, params }) => { await page.waitForSelector('text=1 step completed'); await page.waitForSelector('text=Go to https://www.google.com'); - await page.waitForSelector('text=1.4 s'); + await page.waitForSelector('text=1.42 s'); await page.waitForSelector('text=Complete'); }); diff --git a/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx b/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx index a41dea3b617d4..b3875a052880f 100644 --- a/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx +++ b/x-pack/plugins/synthetics/e2e/page_objects/synthetics/synthetics_app.tsx @@ -98,10 +98,12 @@ export function syntheticsAppPageProvider({ page, kibanaUrl }: { page: Page; kib await page.isVisible('[data-test-subj=monitorSettingsSection]'); }, - async confirmAndSave() { + async confirmAndSave(isUpdate: boolean = false) { await this.ensureIsOnMonitorConfigPage(); await this.clickByTestSubj('syntheticsMonitorConfigSubmitButton'); - return await this.findByText('Monitor added successfully.'); + return await this.findByText( + isUpdate ? 'Monitor updated successfully.' : 'Monitor added successfully.' + ); }, async deleteMonitors() { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/thershold_indicator.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/thershold_indicator.tsx index 26ebe47e045fa..fc77ef52aee8a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/thershold_indicator.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/components/thershold_indicator.tsx @@ -11,29 +11,37 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiIconTip, EuiLoadingContent, + EuiStat, EuiText, EuiToolTip, } from '@elastic/eui'; -export const getDeltaPercent = (current: number, previous: number | null) => { - if (previous === 0 || previous === null) { +export const getDeltaPercent = (current: number, previous?: number | null) => { + if (previous === 0 || previous === null || previous === undefined) { return 0; } - return Number((((current - previous) / previous) * 100).toFixed(0)); }; export const ThresholdIndicator = ({ + description, + helpText, loading, current, previous, previousFormatted, currentFormatted, + asStat = false, }: { + description?: string; + helpText?: string; loading: boolean; current: number; - previous: number | null; - previousFormatted: string; - currentFormatted: string; + previous?: number | null; + previousFormatted?: string | number; + currentFormatted: string | number; + setHasAnyDelta?: (hasDelta: boolean) => void; + asStat?: boolean; }) => { if (loading) { return ; @@ -71,6 +79,50 @@ export const ThresholdIndicator = ({ const hasDelta = Math.abs(delta) > 0; + const content = + previous === null ? ( + + ) : ( + + {hasDelta ? ( + 0 ? 'sortUp' : 'sortDown'} + size={asStat ? 'l' : 'm'} + color={getColor()} + /> + ) : ( + + )} + + ); + + if (asStat) { + return ( + + {description} {helpText && } + + } + title={ + <> + {currentFormatted} + {content} + + } + reverse={true} + /> + ); + } + return ( @@ -78,23 +130,7 @@ export const ThresholdIndicator = ({ {currentFormatted} - {previous !== null && ( - - - {hasDelta ? ( - 0 ? 'sortUp' : 'sortDown'} size="m" color={getColor()} /> - ) : ( - - )} - - - )} + {content} ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx index ec9a1217f8ea4..64ed7b366c245 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/browser_steps_list.tsx @@ -24,6 +24,7 @@ import { StepDetailsLinkIcon } from '../links/step_details_link'; import { parseBadgeStatus, getTextColorForMonitorStatus } from './status_badge'; import { StepDurationText } from './step_duration_text'; +import { ResultDetailsSuccessful } from './result_details_successful'; interface Props { steps: JourneyStep[]; @@ -64,6 +65,8 @@ export const BrowserStepsList = ({ setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); }; + const showLastSuccessful = true; + const columns: Array> = [ ...(showExpand ? [ @@ -146,17 +149,32 @@ export const BrowserStepsList = ({ /> ), }, - { - align: 'left', - name: STEP_DURATION, - render: (item: JourneyStep) => { - return ; - }, - mobileOptions: { - header: STEP_DURATION, - show: true, - }, - }, + ...(showLastSuccessful + ? [ + { + field: 'synthetics.step.status', + name: LAST_SUCCESSFUL, + render: (pingStatus: string, item: JourneyStep) => ( + + ), + }, + ] + : [ + { + align: 'left' as const, + name: STEP_DURATION, + render: (item: JourneyStep) => { + return ; + }, + mobileOptions: { + header: STEP_DURATION, + show: true, + }, + }, + ]), { align: 'right', field: 'timestamp', @@ -175,12 +193,13 @@ export const BrowserStepsList = ({ return ( <> ({ - style: { verticalAlign: 'initial' }, - })} - cellProps={() => ({ - style: { verticalAlign: 'initial' }, - })} + cellProps={(row) => { + if (itemIdToExpandedRowMap[row._id]) { + return { + style: { verticalAlign: 'top' }, + }; + } + }} compressed={compressed} loading={loading} columns={columns} @@ -232,6 +251,9 @@ const RESULT_LABEL = i18n.translate('xpack.synthetics.monitor.result.label', { defaultMessage: 'Result', }); +const LAST_SUCCESSFUL = i18n.translate('xpack.synthetics.monitor.result.lastSuccessful', { + defaultMessage: 'Last successful', +}); const SCREENSHOT_LABEL = i18n.translate('xpack.synthetics.monitor.screenshot.label', { defaultMessage: 'Screenshot', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx index 6f0c5453f920a..e0835dd782414 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details.tsx @@ -6,14 +6,17 @@ */ import React from 'react'; -import { EuiDescriptionList, EuiSpacer } from '@elastic/eui'; -import { formatBytes } from '../../step_details_page/hooks/use_object_metrics'; +import { EuiDescriptionList, EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useStepMetrics } from '../../step_details_page/hooks/use_step_metrics'; +import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container'; import { ThresholdIndicator } from '../components/thershold_indicator'; import { useNetworkTimings } from '../../step_details_page/hooks/use_network_timings'; import { useNetworkTimingsPrevious24Hours } from '../../step_details_page/hooks/use_network_timings_prev'; import { formatMillisecond } from '../../step_details_page/common/network_data/data_formatting'; import { JourneyStep } from '../../../../../../common/runtime_types'; import { parseBadgeStatus, StatusBadge } from './status_badge'; +import { useStepPrevMetrics } from '../../step_details_page/hooks/use_step_prev_metrics'; export const ResultDetails = ({ pingStatus, @@ -26,56 +29,98 @@ export const ResultDetails = ({ }) => { return (
- + + {' '} + {i18n.translate('xpack.synthetics.step.duration.label', { + defaultMessage: 'after {value}', + values: { + value: formatMillisecond((step.synthetics?.step?.duration.us ?? 0) / 1000, {}), + }, + })} + + {isExpanded && ( <> + + + + )}
); }; + export const TimingDetails = ({ step }: { step: JourneyStep }) => { - const { timingsWithLabels, transferSize } = useNetworkTimings( + const { timingsWithLabels } = useNetworkTimings( step.monitor.check_group, step.synthetics.step?.index ); - const { - timingsWithLabels: prevTimingsWithLabels, - loading, - transferSizePrev, - } = useNetworkTimingsPrevious24Hours(step.synthetics.step?.index, step['@timestamp']); + const { timingsWithLabels: prevTimingsWithLabels, loading } = useNetworkTimingsPrevious24Hours( + step.synthetics.step?.index, + step['@timestamp'], + step.monitor.check_group + ); const items = timingsWithLabels?.map((item) => { const prevValueItem = prevTimingsWithLabels?.find((prev) => prev.label === item.label); - const prevValue = prevValueItem?.value ?? 0; + const prevValue = prevValueItem?.value; return { title: item.label, description: ( ), }; }); - items.push({ - title: transferSize.label, - description: ( - - ), + return ( + + ); +}; + +export const StepMetrics = ({ step }: { step: JourneyStep }) => { + const { metrics: stepMetrics } = useStepMetrics(step); + const { metrics: prevMetrics, loading } = useStepPrevMetrics(step); + + const items = stepMetrics?.map((item) => { + const prevValueItem = prevMetrics?.find((prev) => prev.label === item.label); + const prevValue = prevValueItem?.value; + return { + title: item.label, + description: ( + + ), + }; }); return ( @@ -84,7 +129,7 @@ export const TimingDetails = ({ step }: { step: JourneyStep }) => { gutterSize="s" type="column" listItems={items} - style={{ maxWidth: 250 }} + style={{ maxWidth: 265 }} textStyle="reverse" descriptionProps={{ style: { textAlign: 'right' } }} /> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details_successful.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details_successful.tsx new file mode 100644 index 0000000000000..05e6da0b2f8b1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/result_details_successful.tsx @@ -0,0 +1,70 @@ +/* + * 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 { EuiSpacer, EuiText, useEuiTheme } from '@elastic/eui'; +import { useFetcher } from '@kbn/observability-plugin/public'; +import { StepMetrics, TimingDetails } from './result_details'; +import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; +import { JourneyStepScreenshotContainer } from '../screenshot/journey_step_screenshot_container'; +import { formatMillisecond } from '../../step_details_page/common/network_data/data_formatting'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { IMAGE_UN_AVAILABLE } from '../../step_details_page/step_screenshot/last_successful_screenshot'; +import { fetchLastSuccessfulCheck } from '../../../state'; + +export const ResultDetailsSuccessful = ({ + isExpanded, + step, +}: { + isExpanded: boolean; + step: JourneyStep; +}) => { + const { euiTheme } = useEuiTheme(); + + const { data, loading } = useFetcher(() => { + return fetchLastSuccessfulCheck({ + timestamp: step['@timestamp'], + monitorId: step.monitor.id, + stepIndex: Number(step.synthetics.step?.index), + location: step.observer?.geo?.name, + }); + }, [step._id, step['@timestamp']]); + + const { currentStep } = useJourneySteps( + data?.monitor.check_group, + 0, + Number(step.synthetics.step?.index) + ); + + return ( +
+ + {formatMillisecond((currentStep?.synthetics?.step?.duration.us ?? 0) / 1000, {})} + + + {isExpanded && ( + <> + + + + {currentStep && } + + {currentStep && } + + )} +
+ ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/not_found.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/not_found.test.tsx deleted file mode 100644 index 0bade3c639a5e..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/not_found.test.tsx +++ /dev/null @@ -1,19 +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 { NotFoundPage } from './not_found'; -import { render } from '../../../utils/testing'; - -describe('NotFoundPage', () => { - it('render component', async () => { - const { findByText } = render(); - - expect(await findByText('Page not found')).toBeInTheDocument(); - expect(await findByText('Back to home')).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/not_found.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/not_found.tsx deleted file mode 100644 index c94a7a7a06b6a..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/pages/not_found.tsx +++ /dev/null @@ -1,52 +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 { - EuiEmptyPrompt, - EuiPanel, - EuiTitle, - EuiFlexGroup, - EuiFlexItem, - EuiButton, -} from '@elastic/eui'; -import React from 'react'; -import { useHistory } from 'react-router-dom'; -import { FormattedMessage } from '@kbn/i18n-react'; - -export const NotFoundPage = () => { - const history = useHistory(); - return ( - - - - -

- -

- - } - body={ - - - - } - /> -
-
-
- ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx index 1c9b63791a0f3..ac6ebcb7ef360 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/advanced/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiAccordion, EuiDescribedFormGroup, EuiPanel, EuiSpacer } from '@elastic/eui'; import { useFormContext, FieldError } from 'react-hook-form'; @@ -14,14 +14,18 @@ import { FORM_CONFIG } from '../form/form_config'; import { Field } from '../form/field'; import { ConfigKey, FormMonitorType } from '../types'; -export const AdvancedConfig = () => { +export const AdvancedConfig = ({ readOnly }: { readOnly: boolean }) => { const { watch, formState: { errors }, } = useFormContext(); const [type]: [FormMonitorType] = watch([ConfigKey.FORM_MONITOR_TYPE]); - return FORM_CONFIG[type]?.advanced ? ( + const formConfig = useMemo(() => { + return FORM_CONFIG(readOnly)[type]; + }, [readOnly, type]); + + return formConfig?.advanced ? ( { })} > - {FORM_CONFIG[type].advanced?.map((configGroup) => { + {formConfig.advanced?.map((configGroup) => { return ( { return ( @@ -52,6 +54,7 @@ export const CodeEditor = ({ onChange={onChange} options={{ renderValidationDecorations: value ? 'on' : 'off', + readOnly, }} isCopyable={true} allowFullScreen={true} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx index 75dfd1aa8e04e..a26fe8616d90b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/header_field.tsx @@ -17,6 +17,7 @@ export interface HeaderFieldProps { onChange: (value: Record) => void; onBlur?: () => void; 'data-test-subj'?: string; + readOnly?: boolean; } export const HeaderField = ({ @@ -25,6 +26,7 @@ export const HeaderField = ({ onChange, onBlur, 'data-test-subj': dataTestSubj, + readOnly, }: HeaderFieldProps) => { const defaultValueKeys = Object.keys(defaultValue).filter((key) => key !== 'Content-Type'); // Content-Type is a secret header we hide from the user const formattedDefaultValues: Pair[] = [ @@ -65,6 +67,7 @@ export const HeaderField = ({ onChange={setHeaders} onBlur={() => onBlur?.()} data-test-subj={dataTestSubj} + readOnly={readOnly} /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/index_response_body_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/index_response_body_field.tsx index 71c11bc01e7f9..dc6da249aab2d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/index_response_body_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/index_response_body_field.tsx @@ -16,12 +16,14 @@ export interface ResponseBodyIndexFieldProps { defaultValue: ResponseBodyIndexPolicy; onChange: (responseBodyIndexPolicy: ResponseBodyIndexPolicy) => void; onBlur?: () => void; + readOnly?: boolean; } export const ResponseBodyIndexField = ({ defaultValue, onChange, onBlur, + readOnly, }: ResponseBodyIndexFieldProps) => { const [policy, setPolicy] = useState( defaultValue !== ResponseBodyIndexPolicy.NEVER ? defaultValue : ResponseBodyIndexPolicy.ON_ERROR @@ -49,7 +51,7 @@ export const ResponseBodyIndexField = ({ checked={checked} label={ } @@ -58,13 +60,14 @@ export const ResponseBodyIndexField = ({ setChecked(checkedEvent); }} onBlur={() => onBlur?.()} + disabled={readOnly} />
{checked && ( onBlur?.()} + disabled={readOnly} /> )} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx index 153d537ebec0e..95b2348fd0219 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/key_value_field.tsx @@ -52,6 +52,7 @@ interface Props { onChange: (pairs: Pair[]) => void; onBlur?: () => void; 'data-test-subj'?: string; + readOnly?: boolean; } export const KeyValuePairsField = ({ @@ -60,6 +61,7 @@ export const KeyValuePairsField = ({ onChange, onBlur, 'data-test-subj': dataTestSubj, + readOnly, }: Props) => { const [pairs, setPairs] = useState(defaultPairs); @@ -105,6 +107,7 @@ export const KeyValuePairsField = ({ iconType="plus" onClick={handleAddPair} data-test-subj={`${dataTestSubj}__button`} + isDisabled={readOnly} > {addPairControlLabel} @@ -158,6 +161,7 @@ export const KeyValuePairsField = ({ } )} onClick={() => handleDeletePair(index)} + isDisabled={readOnly} /> } @@ -173,6 +177,7 @@ export const KeyValuePairsField = ({ value={key} onChange={(event) => handleOnChange(event, index, true)} onBlur={() => onBlur?.()} + readOnly={readOnly} /> } endControl={ @@ -187,6 +192,7 @@ export const KeyValuePairsField = ({ value={value} onChange={(event) => handleOnChange(event, index, false)} onBlur={() => onBlur?.()} + readOnly={readOnly} /> } delimiter=":" diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx index f9eb53fc36bbd..6d731f0e34ce9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/fields/request_body_field.tsx @@ -20,6 +20,7 @@ export interface RequestBodyFieldProps { type: Mode; value: string; }; + readOnly?: boolean; } enum ResponseBodyType { @@ -32,6 +33,7 @@ export const RequestBodyField = ({ onChange, onBlur, value: { type, value }, + readOnly, }: RequestBodyFieldProps) => { const [values, setValues] = useState>({ [ResponseBodyType.FORM]: type === Mode.FORM ? value : '', @@ -105,6 +107,7 @@ export const RequestBodyField = ({ onBlur?.(); }} value={values[ResponseBodyType.CODE]} + readOnly /> ), }, @@ -127,6 +130,7 @@ export const RequestBodyField = ({ onBlur?.(); }} value={values[ResponseBodyType.CODE]} + readOnly /> ), }, @@ -149,6 +153,7 @@ export const RequestBodyField = ({ onBlur?.(); }} value={values[ResponseBodyType.CODE]} + readOnly /> ), }, @@ -167,20 +172,24 @@ export const RequestBodyField = ({ defaultPairs={defaultFormPairs} onChange={onChangeFormFields} onBlur={() => onBlur?.()} + readOnly /> ), }, ]; return ( - tab.id === type)} - autoFocus="selected" - onTabClick={(tab) => { - handleSetMode(tab.id as Mode); - }} - /> +
+ tab.id === type)} + autoFocus="selected" + onTabClick={(tab) => { + handleSetMode(tab.id as Mode); + }} + /> +
); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx index 59ecdd39617a4..72de842731ccc 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/controlled_field.tsx @@ -15,11 +15,11 @@ import { } from 'react-hook-form'; import { useKibanaSpace, useIsEditFlow } from '../hooks'; import { selectServiceLocationsState } from '../../../state'; -import { FieldMeta } from '../types'; +import { FieldMeta, FormConfig } from '../types'; -type Props = FieldMeta & { +type Props = FieldMeta & { component: React.ComponentType; - field: ControllerRenderProps; + field: ControllerRenderProps; fieldState: ControllerFieldState; formRowProps: Partial; error: React.ReactNode; @@ -27,11 +27,12 @@ type Props = FieldMeta & { dependenciesFieldMeta: Record; }; -const setFieldValue = (key: string, setValue: UseFormReturn['setValue']) => (value: any) => { - setValue(key, value); -}; +const setFieldValue = + (key: keyof FormConfig, setValue: UseFormReturn['setValue']) => (value: any) => { + setValue(key, value); + }; -export const ControlledField = ({ +export const ControlledField = ({ component: FieldComponent, props, fieldKey, @@ -43,8 +44,8 @@ export const ControlledField = ({ error, dependenciesValues, dependenciesFieldMeta, -}: Props) => { - const { setValue, reset, formState, setError, clearErrors } = useFormContext(); +}: Props) => { + const { setValue, reset, formState, setError, clearErrors } = useFormContext(); const noop = () => {}; let hook: Function = noop; let hookProps; @@ -62,7 +63,7 @@ export const ControlledField = ({ field, setValue, reset, - locations, + locations: locations.map((location) => ({ ...location, key: location.id })), dependencies: dependenciesValues, dependenciesFieldMeta, space: space?.id, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx index fbb0943b5f773..7aed077680d4b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field.tsx @@ -11,9 +11,9 @@ import { EuiFormRow } from '@elastic/eui'; import { selectServiceLocationsState } from '../../../state'; import { useKibanaSpace, useIsEditFlow } from '../hooks'; import { ControlledField } from './controlled_field'; -import { FieldMeta } from '../types'; +import { FormConfig, FieldMeta } from '../types'; -type Props = FieldMeta & { fieldError?: FieldError }; +type Props = FieldMeta & { fieldError?: FieldError }; export const Field = memo( ({ @@ -34,7 +34,7 @@ export const Field = memo( customHook, }: Props) => { const { register, watch, control, setValue, reset, getFieldState, formState } = - useFormContext(); + useFormContext(); const { locations } = useSelector(selectServiceLocationsState); const { space } = useKibanaSpace(); const isEdit = useIsEditFlow(); @@ -76,7 +76,7 @@ export const Field = memo( }; return controlled ? ( - control={control} name={fieldKey} rules={{ @@ -118,7 +118,7 @@ export const Field = memo( formState, setValue, reset, - locations, + locations: locations.map((location) => ({ ...location, key: location.id })), dependencies: dependenciesValues, dependenciesFieldMeta, space: space?.id, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx index 4711007fef063..d28f8f7f39eb0 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_config.tsx @@ -9,17 +9,25 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { isValidNamespace } from '@kbn/fleet-plugin/common'; -import { UseFormReturn, ControllerRenderProps, FormState } from 'react-hook-form'; import { EuiCode, EuiComboBoxOptionOption, - EuiComboBoxProps, EuiFlexGroup, EuiFlexItem, EuiSuperSelect, EuiText, EuiLink, EuiTextArea, + EuiSelectProps, + EuiFieldTextProps, + EuiSwitchProps, + EuiComboBoxProps, + EuiFieldNumberProps, + EuiFieldPasswordProps, + EuiCheckboxProps, + EuiTextAreaProps, + EuiButtonGroupProps, + EuiSuperSelectProps, } from '@elastic/eui'; import { FieldText, @@ -32,13 +40,18 @@ import { Source, ButtonGroup, FormattedComboBox, + FormattedComboBoxProps, JSONEditor, + JSONCodeEditorProps, MonitorTypeRadioGroup, HeaderField, + HeaderFieldProps, RequestBodyField, + RequestBodyFieldProps, ResponseBodyIndexField, + ResponseBodyIndexFieldProps, + ControlledFieldProp, } from './field_wrappers'; -import { formatLocation } from '../../../../../../common/utils/location_formatter'; import { getDocLinks } from '../../../../../kibana_services'; import { useMonitorName } from '../hooks/use_monitor_name'; import { @@ -46,14 +59,12 @@ import { DataStream, FormMonitorType, HTTPMethod, - MonitorFields, - MonitorServiceLocations, ScreenshotOption, - ServiceLocations, - SyntheticsMonitor, + MonitorFields, TLSVersion, VerificationMode, - FieldMeta, + FieldMap, + FormLocation, } from '../types'; import { AlertConfigKey, DEFAULT_BROWSER_ADVANCED_FIELDS } from '../constants'; import { getDefaultFormFields } from './defaults'; @@ -190,7 +201,7 @@ export const MONITOR_TYPE_CONFIG = { }, }; -export const FIELD: Record = { +export const FIELD = (readOnly?: boolean): FieldMap => ({ [ConfigKey.FORM_MONITOR_TYPE]: { fieldKey: ConfigKey.FORM_MONITOR_TYPE, required: true, @@ -211,7 +222,7 @@ export const FIELD: Record = { required: true, }), }, - [`${ConfigKey.URLS}__single`]: { + [`urls__single`]: { fieldKey: ConfigKey.URLS, required: true, component: FieldText, @@ -223,7 +234,7 @@ export const FIELD: Record = { }), controlled: true, dependencies: [ConfigKey.NAME], - props: ({ setValue, dependenciesFieldMeta, isEdit, formState }) => { + props: ({ setValue, dependenciesFieldMeta, isEdit, formState }): EuiFieldTextProps => { return { 'data-test-subj': 'syntheticsMonitorConfigURL', onChange: (event: React.ChangeEvent) => { @@ -236,10 +247,11 @@ export const FIELD: Record = { }); } }, + readOnly, }; }, }, - [`${ConfigKey.URLS}__http`]: { + [`urls__http`]: { fieldKey: ConfigKey.URLS, required: true, component: FieldText, @@ -251,7 +263,7 @@ export const FIELD: Record = { }), controlled: true, dependencies: [ConfigKey.NAME], - props: ({ setValue, dependenciesFieldMeta, isEdit, formState }) => { + props: ({ setValue, dependenciesFieldMeta, isEdit, formState }): EuiFieldTextProps => { return { onChange: (event: React.ChangeEvent) => { setValue(ConfigKey.URLS, event.target.value, { @@ -264,10 +276,11 @@ export const FIELD: Record = { } }, 'data-test-subj': 'syntheticsMonitorConfigURL', + readOnly, }; }, }, - [`${ConfigKey.HOSTS}__tcp`]: { + [`hosts__tcp`]: { fieldKey: ConfigKey.HOSTS, required: true, component: FieldText, @@ -276,7 +289,7 @@ export const FIELD: Record = { }), controlled: true, dependencies: [ConfigKey.NAME], - props: ({ setValue, dependenciesFieldMeta, isEdit, formState }) => { + props: ({ setValue, dependenciesFieldMeta, isEdit, formState }): EuiFieldTextProps => { return { onChange: (event: React.ChangeEvent) => { setValue(ConfigKey.HOSTS, event.target.value, { @@ -289,10 +302,11 @@ export const FIELD: Record = { } }, 'data-test-subj': 'syntheticsMonitorConfigHost', + readOnly, }; }, }, - [`${ConfigKey.HOSTS}__icmp`]: { + [`hosts__icmp`]: { fieldKey: ConfigKey.HOSTS, required: true, component: FieldText, @@ -301,7 +315,7 @@ export const FIELD: Record = { }), controlled: true, dependencies: [ConfigKey.NAME], - props: ({ setValue, dependenciesFieldMeta, isEdit, formState }) => { + props: ({ setValue, dependenciesFieldMeta, isEdit, formState }): EuiFieldTextProps => { return { onChange: (event: React.ChangeEvent) => { setValue(ConfigKey.HOSTS, event.target.value, { @@ -314,6 +328,7 @@ export const FIELD: Record = { } }, 'data-test-subj': 'syntheticsMonitorConfigHost', + readOnly, }; }, }, @@ -345,11 +360,12 @@ export const FIELD: Record = { error: i18n.translate('xpack.synthetics.monitorConfig.name.error', { defaultMessage: 'Monitor name is required', }), - props: () => ({ + props: (): EuiFieldTextProps => ({ 'data-test-subj': 'syntheticsMonitorConfigName', + readOnly, }), }, - [ConfigKey.SCHEDULE]: { + ['schedule.number']: { fieldKey: `${ConfigKey.SCHEDULE}.number`, required: true, component: Select, @@ -361,11 +377,12 @@ export const FIELD: Record = { 'How often do you want to run this test? Higher frequencies will increase your total cost.', }), dependencies: [ConfigKey.MONITOR_TYPE], - props: ({ dependencies }) => { + props: ({ dependencies }): EuiSelectProps => { const [monitorType] = dependencies; return { 'data-test-subj': 'syntheticsMonitorConfigSchedule', options: monitorType === DataStream.BROWSER ? BROWSER_SCHEDULES : LIGHTWEIGHT_SCHEDULES, + disabled: readOnly, }; }, }, @@ -373,7 +390,7 @@ export const FIELD: Record = { fieldKey: ConfigKey.LOCATIONS, required: true, controlled: true, - component: ComboBox as React.ComponentType>, + component: ComboBox, label: i18n.translate('xpack.synthetics.monitorConfig.locations.label', { defaultMessage: 'Locations', }), @@ -381,39 +398,26 @@ export const FIELD: Record = { defaultMessage: 'Where do you want to run this test from? Additional locations will increase your total cost.', }), - props: ({ - field, - setValue, - locations, - formState, - }: { - field?: ControllerRenderProps; - setValue: UseFormReturn['setValue']; - locations: ServiceLocations; - formState: FormState; - }) => { + props: ({ field, setValue, locations, formState }) => { return { options: Object.values(locations).map((location) => ({ - label: locations?.find((loc) => location.id === loc.id)?.label, - id: location.id, - key: location.id, - isServiceManaged: location.isServiceManaged, + label: locations?.find((loc) => location.id === loc.id)?.label || '', + id: location.id || '', + isServiceManaged: location.isServiceManaged || false, })), - selectedOptions: Object.values(field?.value as ServiceLocations).map((location) => ({ + selectedOptions: Object.values(field?.value || {}).map((location) => ({ color: locations.some((s) => s.id === location.id) ? 'default' : 'danger', label: locations?.find((loc) => location.id === loc.id)?.label ?? location.id, - id: location.id, - key: location.id, - isServiceManaged: location.isServiceManaged, + id: location.id || '', + isServiceManaged: location.isServiceManaged || false, })), 'data-test-subj': 'syntheticsMonitorConfigLocations', - onChange: (updatedValues: ServiceLocations) => { - setValue( - ConfigKey.LOCATIONS, - updatedValues.map((location) => formatLocation(location)) as MonitorServiceLocations, - { shouldValidate: Boolean(formState.submitCount > 0) } - ); + onChange: (updatedValues: FormLocation[]) => { + setValue(ConfigKey.LOCATIONS, updatedValues, { + shouldValidate: Boolean(formState.submitCount > 0), + }); }, + isDisabled: readOnly, }; }, }, @@ -424,7 +428,7 @@ export const FIELD: Record = { defaultMessage: 'Enable Monitor', }), controlled: true, - props: ({ isEdit, setValue }) => ({ + props: ({ isEdit, setValue, field }): EuiSwitchProps => ({ id: 'syntheticsMontiorConfigIsEnabled', label: isEdit ? i18n.translate('xpack.synthetics.monitorConfig.edit.enabled.label', { @@ -434,19 +438,21 @@ export const FIELD: Record = { defaultMessage: 'Disabled monitors do not run tests. You can create a disabled monitor and enable it later.', }), - onChange: (event: React.ChangeEvent) => { + checked: field?.value || false, + onChange: (event) => { setValue(ConfigKey.ENABLED, !!event.target.checked); }, + 'data-test-subj': 'syntheticsEnableSwitch', }), }, - [ConfigKey.ALERT_CONFIG]: { + [AlertConfigKey.STATUS_ENABLED]: { fieldKey: AlertConfigKey.STATUS_ENABLED, component: Switch, label: i18n.translate('xpack.synthetics.monitorConfig.enabledAlerting.label', { defaultMessage: 'Enable status alerts', }), controlled: true, - props: ({ isEdit, setValue, field }) => ({ + props: ({ isEdit, setValue, field }): EuiSwitchProps => ({ id: 'syntheticsMonitorConfigIsAlertEnabled', label: isEdit ? i18n.translate('xpack.synthetics.monitorConfig.edit.alertEnabled.label', { @@ -455,9 +461,11 @@ export const FIELD: Record = { : i18n.translate('xpack.synthetics.monitorConfig.create.alertEnabled.label', { defaultMessage: 'Enable status alerts on this monitor.', }), - onChange: (event: React.ChangeEvent) => { + checked: field?.value || false, + onChange: (event) => { setValue(AlertConfigKey.STATUS_ENABLED, !!event.target.checked); }, + disabled: readOnly, }), }, [ConfigKey.TAGS]: { @@ -471,8 +479,11 @@ export const FIELD: Record = { 'A list of tags that will be sent with each monitor event. Useful for searching and segmenting data.', }), controlled: true, - props: ({ field }) => ({ - selectedOptions: field?.value, + props: ({ + field, + }): Omit, 'selectedOptions'> & FormattedComboBoxProps => ({ + selectedOptions: field?.value || [], + isDisabled: readOnly, }), }, [ConfigKey.TIMEOUT]: { @@ -484,9 +495,10 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.timeout.helpText', { defaultMessage: 'The total time allowed for testing the connection and exchanging data.', }), - props: () => ({ + props: (): EuiFieldNumberProps => ({ min: 1, step: 'any', + readOnly, }), dependencies: [ConfigKey.SCHEDULE], validation: ([schedule]) => { @@ -523,9 +535,9 @@ export const FIELD: Record = { 'Corresponds to the service.name ECS field from APM. Set this to enable integrations between APM and Synthetics data.', }), controlled: true, - props: ({ field }) => ({ - selectedOptions: field?.value, + props: (): EuiFieldTextProps => ({ 'data-test-subj': 'syntheticsMonitorConfigAPMServiceName', + readOnly, }), }, [ConfigKey.NAMESPACE]: { @@ -548,8 +560,8 @@ export const FIELD: Record = { ), controlled: true, - props: ({ field }) => ({ - selectedOptions: field, + props: (): EuiFieldTextProps => ({ + readOnly, }), validation: () => ({ validate: (namespace) => isValidNamespace(namespace).error, @@ -564,10 +576,11 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.maxRedirects.helpText', { defaultMessage: 'The total number of redirects to follow.', }), - props: () => ({ + props: (): EuiFieldNumberProps => ({ min: 0, max: 10, step: 1, + readOnly, }), validation: () => ({ min: 0, @@ -587,9 +600,10 @@ export const FIELD: Record = { defaultMessage: 'The duration to wait before emitting another ICMP Echo Request if no response is received.', }), - props: () => ({ + props: (): EuiFieldNumberProps => ({ min: 1, step: 1, + readOnly, }), validation: () => ({ min: 1, @@ -608,6 +622,9 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.username.helpText', { defaultMessage: 'Username for authenticating with the server.', }), + props: (): EuiFieldTextProps => ({ + readOnly, + }), }, [ConfigKey.PASSWORD]: { fieldKey: ConfigKey.PASSWORD, @@ -618,6 +635,9 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.password.helpText', { defaultMessage: 'Password for authenticating with the server.', }), + props: (): EuiFieldPasswordProps => ({ + readOnly, + }), }, [ConfigKey.PROXY_URL]: { fieldKey: ConfigKey.PROXY_URL, @@ -628,6 +648,9 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.proxyUrl.helpText', { defaultMessage: 'HTTP proxy URL', }), + props: (): EuiFieldTextProps => ({ + readOnly, + }), }, [ConfigKey.REQUEST_METHOD_CHECK]: { fieldKey: ConfigKey.REQUEST_METHOD_CHECK, @@ -638,11 +661,12 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.requestMethod.helpText', { defaultMessage: 'The HTTP method to use.', }), - props: () => ({ + props: (): EuiSelectProps => ({ options: Object.keys(HTTPMethod).map((method) => ({ value: method, text: method, })), + disabled: readOnly, }), }, [ConfigKey.REQUEST_HEADERS_CHECK]: { @@ -662,6 +686,9 @@ export const FIELD: Record = { error: i18n.translate('xpack.synthetics.monitorConfig.requestHeaders.error', { defaultMessage: 'Header key must be a valid HTTP token.', }), + props: (): HeaderFieldProps => ({ + readOnly, + }), }, [ConfigKey.REQUEST_BODY_CHECK]: { fieldKey: ConfigKey.REQUEST_BODY_CHECK, @@ -673,6 +700,9 @@ export const FIELD: Record = { defaultMessage: 'Request body content.', }), controlled: true, + props: (): RequestBodyFieldProps => ({ + readOnly, + }), }, [ConfigKey.RESPONSE_HEADERS_INDEX]: { fieldKey: ConfigKey.RESPONSE_HEADERS_INDEX, @@ -686,11 +716,12 @@ export const FIELD: Record = { http.response.body.headers ), - props: () => ({ + props: (): Omit => ({ label: i18n.translate('xpack.synthetics.monitorConfig.indexResponseHeaders.label', { defaultMessage: 'Index response headers', }), - id: 'syntheticsMonitorConfigResponseHeadersIndex', // checkbox needs an id or it won't work + id: 'syntheticsMonitorConfigResponseHeadersIndex', // checkbox needs an id or it won't work, + disabled: readOnly, }), controlled: true, }, @@ -706,10 +737,8 @@ export const FIELD: Record = { http.response.body.contents ), - props: () => ({ - label: i18n.translate('xpack.synthetics.monitorConfig.indexResponseBody.label', { - defaultMessage: 'Index response body', - }), + props: (): ResponseBodyIndexFieldProps => ({ + readOnly, }), controlled: true, }, @@ -724,8 +753,9 @@ export const FIELD: Record = { 'A list of expected status codes. Press enter to add a new code. 4xx and 5xx codes are considered down by default. Other codes are considered up.', }), controlled: true, - props: ({ field }) => ({ + props: ({ field }): EuiComboBoxProps => ({ selectedOptions: field?.value, + isDisabled: readOnly, }), validation: () => ({ validate: (value) => { @@ -757,6 +787,9 @@ export const FIELD: Record = { error: i18n.translate('xpack.synthetics.monitorConfig.responseHeadersCheck.error', { defaultMessage: 'Header key must be a valid HTTP token.', }), + props: (): HeaderFieldProps => ({ + readOnly, + }), }, [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: { fieldKey: ConfigKey.RESPONSE_BODY_CHECK_POSITIVE, @@ -769,8 +802,9 @@ export const FIELD: Record = { 'A list of regular expressions to match the body output. Press enter to add a new expression. Only a single expression needs to match.', }), controlled: true, - props: ({ field }) => ({ + props: ({ field }): EuiComboBoxProps => ({ selectedOptions: field?.value, + isDisabled: readOnly, }), }, [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: { @@ -784,8 +818,9 @@ export const FIELD: Record = { 'A list of regular expressions to match the the body output negatively. Press enter to add a new expression. Return match failed if single expression matches.', }), controlled: true, - props: ({ field }) => ({ + props: ({ field }): EuiComboBoxProps => ({ selectedOptions: field?.value, + isDisabled: readOnly, }), }, [ConfigKey.RESPONSE_RECEIVE_CHECK]: { @@ -797,8 +832,11 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.responseReceiveCheck.helpText', { defaultMessage: 'The expected remote host response.', }), + props: (): EuiFieldTextProps => ({ + readOnly, + }), }, - [`${ConfigKey.PROXY_URL}__tcp`]: { + ['proxy_url__tcp']: { fieldKey: ConfigKey.PROXY_URL, component: FieldText, label: i18n.translate('xpack.synthetics.monitorConfig.proxyURLTCP.label', { @@ -808,6 +846,9 @@ export const FIELD: Record = { defaultMessage: 'The URL of the SOCKS5 proxy to use when connecting to the server. The value must be a URL with a scheme of socks5://.', }), + props: (): EuiFieldTextProps => ({ + readOnly, + }), }, [ConfigKey.REQUEST_SEND_CHECK]: { fieldKey: ConfigKey.REQUEST_SEND_CHECK, @@ -818,8 +859,11 @@ export const FIELD: Record = { helpText: i18n.translate('xpack.synthetics.monitorConfig.requestSendCheck.helpText', { defaultMessage: 'A payload string to send to the remote host.', }), + props: (): EuiFieldTextProps => ({ + readOnly, + }), }, - [ConfigKey.SOURCE_INLINE]: { + ['source.inline']: { fieldKey: 'source.inline', required: true, component: Source, @@ -843,12 +887,13 @@ export const FIELD: Record = { defaultMessage: 'Parameters', }), component: JSONEditor, - props: ({ setValue }) => ({ + props: ({ setValue }): JSONCodeEditorProps => ({ id: 'syntheticsMonitorConfigParams', height: '100px', - onChange: (json: string) => { - setValue(ConfigKey.PARAMS, json); - }, + ariaLabel: i18n.translate('xpack.synthetics.monitorConfig.paramsAria.label', { + defaultMessage: 'Monitor params code editor', + }), + readOnly, }), error: i18n.translate('xpack.synthetics.monitorConfig.params.error', { defaultMessage: 'Invalid JSON format', @@ -877,15 +922,17 @@ export const FIELD: Record = { fieldKey: 'isTLSEnabled', component: Switch, controlled: true, - props: ({ setValue }) => { + props: ({ setValue, field }): EuiSwitchProps => { return { id: 'syntheticsMontiorConfigIsTLSEnabledSwitch', label: i18n.translate('xpack.synthetics.monitorConfig.customTLS.label', { defaultMessage: 'Use custom TLS configuration', }), - onChange: (event: React.ChangeEvent) => { + checked: field?.value || false, + onChange: (event) => { setValue('isTLSEnabled', event.target.checked); }, + disabled: readOnly, }; }, }, @@ -900,34 +947,29 @@ export const FIELD: Record = { 'Verifies that the provided certificate is signed by a trusted authority (CA) and also verifies that the server’s hostname (or IP address) matches the names identified within the certificate. If the Subject Alternative Name is empty, it returns an error.', }), showWhen: ['isTLSEnabled', true], - props: () => ({ + props: (): EuiSelectProps => ({ options: Object.values(VerificationMode).map((method) => ({ value: method, text: method.toUpperCase(), })), + disabled: readOnly, }), }, [ConfigKey.TLS_VERSION]: { fieldKey: ConfigKey.TLS_VERSION, - component: ComboBox as React.ComponentType>, + component: ComboBox, label: i18n.translate('xpack.synthetics.monitorConfig.tlsVersion.label', { defaultMessage: 'Supported TLS protocols', }), controlled: true, showWhen: ['isTLSEnabled', true], - props: ({ - field, - setValue, - }: { - field?: ControllerRenderProps; - setValue: UseFormReturn['setValue']; - }) => { + props: ({ field, setValue }): EuiComboBoxProps => { return { options: Object.values(TLSVersion).map((version) => ({ label: version, })), - selectedOptions: Object.values(field?.value).map((version) => ({ - label: version, + selectedOptions: Object.values(field?.value || []).map((version) => ({ + label: version as TLSVersion, })), onChange: (updatedValues: Array>) => { setValue( @@ -935,6 +977,7 @@ export const FIELD: Record = { updatedValues.map((option) => option.label as TLSVersion) ); }, + isDisabled: readOnly, }; }, }, @@ -948,6 +991,9 @@ export const FIELD: Record = { defaultMessage: 'PEM-formatted custom certificate authorities.', }), showWhen: ['isTLSEnabled', true], + props: (): EuiTextAreaProps => ({ + readOnly, + }), }, [ConfigKey.TLS_CERTIFICATE]: { fieldKey: ConfigKey.TLS_CERTIFICATE, @@ -959,6 +1005,9 @@ export const FIELD: Record = { defaultMessage: 'PEM-formatted certificate for TLS client authentication.', }), showWhen: ['isTLSEnabled', true], + props: (): EuiTextAreaProps => ({ + readOnly, + }), }, [ConfigKey.TLS_KEY]: { fieldKey: ConfigKey.TLS_KEY, @@ -970,6 +1019,9 @@ export const FIELD: Record = { defaultMessage: 'PEM-formatted certificate key for TLS client authentication.', }), showWhen: ['isTLSEnabled', true], + props: (): EuiTextAreaProps => ({ + readOnly, + }), }, [ConfigKey.TLS_KEY_PASSPHRASE]: { fieldKey: ConfigKey.TLS_KEY_PASSPHRASE, @@ -981,6 +1033,9 @@ export const FIELD: Record = { defaultMessage: 'Certificate key passphrase for TLS client authentication.', }), showWhen: ['isTLSEnabled', true], + props: (): EuiFieldPasswordProps => ({ + readOnly, + }), }, [ConfigKey.SCREENSHOTS]: { fieldKey: ConfigKey.SCREENSHOTS, @@ -992,23 +1047,20 @@ export const FIELD: Record = { defaultMessage: 'Set this option to manage the screenshots captured by the synthetics agent.', }), controlled: true, - props: ({ - field, - setValue, - }: { - field?: ControllerRenderProps; - setValue: UseFormReturn['setValue']; - }) => ({ - type: 'single', + props: ({ field, setValue }): Omit => ({ idSelected: field?.value, - onChange: (option: ScreenshotOption) => setValue(ConfigKey.SCREENSHOTS, option), + onChange: (option: string) => setValue(ConfigKey.SCREENSHOTS, option), options: Object.values(ScreenshotOption).map((option) => ({ id: option, label: option.replace(/-/g, ' '), })), + legend: i18n.translate('xpack.synthetics.monitorConfig.screenshotOptions.label', { + defaultMessage: 'Screenshot options', + }), css: { textTransform: 'capitalize', }, + isDisabled: readOnly, }), }, [ConfigKey.TEXT_ASSERTION]: { @@ -1024,6 +1076,9 @@ export const FIELD: Record = { validation: () => ({ required: true, }), + props: (): EuiFieldTextProps => ({ + readOnly, + }), }, [ConfigKey.THROTTLING_CONFIG]: { fieldKey: ConfigKey.THROTTLING_CONFIG, @@ -1037,7 +1092,7 @@ export const FIELD: Record = { defaultMessage: 'Simulate network throttling (download, upload, latency). More options will be added in a future version.', }), - props: () => ({ + props: (): EuiSuperSelectProps => ({ options: [ { value: DEFAULT_BROWSER_ADVANCED_FIELDS[ConfigKey.THROTTLING_CONFIG], @@ -1059,6 +1114,7 @@ export const FIELD: Record = { ), }, ], + readOnly, disabled: true, // currently disabled through 1.0 until we define connection profiles }), validation: () => ({ @@ -1097,14 +1153,15 @@ export const FIELD: Record = { ), controlled: true, required: false, - props: ({ - field, - setValue, - }: { - field?: ControllerRenderProps; - setValue: UseFormReturn['setValue']; - }) => ({ - onChange: (json: string) => setValue(ConfigKey.PLAYWRIGHT_OPTIONS, json), + props: (): JSONCodeEditorProps => ({ + ariaLabel: i18n.translate( + 'xpack.synthetics.monitorConfig.playwrightOptions.codeEditor.json.ariaLabel', + { + defaultMessage: 'Playwright options JSON code editor', + } + ), + readOnly, + id: 'syntheticsPlaywrightOptionsJSONCodeEditor', }), validation: () => ({ validate: (value) => { @@ -1137,11 +1194,12 @@ export const FIELD: Record = { onChange: (event: React.ChangeEvent) => { setValue(ConfigKey.IGNORE_HTTPS_ERRORS, !!event.target.checked); }, + disabled: readOnly, }), }, [ConfigKey.SYNTHETICS_ARGS]: { fieldKey: ConfigKey.SYNTHETICS_ARGS, - component: FieldText, + component: ComboBox as React.ComponentType>, controlled: true, label: i18n.translate('xpack.synthetics.monitorConfig.syntheticsArgs.label', { defaultMessage: 'Synthetics args', @@ -1154,11 +1212,21 @@ export const FIELD: Record = { })} ), - props: ({ setValue }) => ({ + props: ({ setValue, field }): EuiComboBoxProps => ({ id: 'syntheticsMontiorConfigSyntheticsArgs', - onChange: (event: React.ChangeEvent) => { - setValue(ConfigKey.SYNTHETICS_ARGS, event.target.value); + selectedOptions: Object.values(field?.value || []).map((arg) => ({ + label: arg, + })), + onChange: (updatedValues: Array>) => { + setValue( + ConfigKey.SYNTHETICS_ARGS, + updatedValues.map((option) => option.label) + ); + }, + onCreateOption: (newValue: string) => { + setValue(ConfigKey.SYNTHETICS_ARGS, [...(field?.value || []), newValue]); }, + isDisabled: readOnly, }), }, -}; +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx index 002d536aec26e..ad4ad3635e830 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/field_wrappers.tsx @@ -7,6 +7,7 @@ import React, { Ref } from 'react'; import { omit } from 'lodash'; +import { ControllerRenderProps } from 'react-hook-form'; import { EuiFieldText, EuiFieldTextProps, @@ -28,23 +29,44 @@ import { import { SourceField, SourceFieldProps } from '../fields/source_field'; import { FormattedComboBox as DefaultFormattedComboBox, - FormattedComboBoxProps, + FormattedComboBoxProps as DefaultFormattedComboBoxProps, } from '../fields/combo_box'; -import { JSONEditor as DefaultJSONEditor, CodeEditorProps } from '../fields/code_editor'; +import { + JSONEditor as DefaultJSONEditor, + CodeEditorProps as DefaultCodeEditorProps, +} from '../fields/code_editor'; import { MonitorTypeRadioGroup as DefaultMonitorTypeRadioGroup, MonitorTypeRadioGroupProps, } from '../fields/monitor_type_radio_group'; -import { HeaderField as DefaultHeaderField, HeaderFieldProps } from '../fields/header_field'; +import { + HeaderField as DefaultHeaderField, + HeaderFieldProps as DefaultHeaderFieldProps, +} from '../fields/header_field'; import { RequestBodyField as DefaultRequestBodyField, - RequestBodyFieldProps, + RequestBodyFieldProps as DefaultRequestBodyFieldProps, } from '../fields/request_body_field'; import { ResponseBodyIndexField as DefaultResponseBodyIndexField, - ResponseBodyIndexFieldProps, + ResponseBodyIndexFieldProps as DefaultResponseBodyIndexFieldProps, } from '../fields/index_response_body_field'; +// these props are automatically passed through to our controlled components +// they do not have to be defined specifically on the 'props' field in the +// `field_config` file +export type ControlledFieldProp = keyof ControllerRenderProps | 'defaultValue'; + +export type HeaderFieldProps = Omit; +export type ResponseBodyIndexFieldProps = Omit< + DefaultResponseBodyIndexFieldProps, + ControlledFieldProp +>; +export type RequestBodyFieldProps = Omit; +export type CodeEditorProps = Omit; +export type JSONCodeEditorProps = Omit; +export type FormattedComboBoxProps = Omit; + export const FieldText = React.forwardRef( (props, ref: Ref) => ( ((props )); -export const FormattedComboBox = React.forwardRef( +export const FormattedComboBox = React.forwardRef( (props, _ref) => ); @@ -90,7 +112,7 @@ export const ComboBox = React.forwardRef>((pr )); -export const JSONEditor = React.forwardRef((props, _ref) => ( +export const JSONEditor = React.forwardRef((props, _ref) => ( )); @@ -98,14 +120,14 @@ export const MonitorTypeRadioGroup = React.forwardRef ); -export const HeaderField = React.forwardRef((props, _ref) => ( +export const HeaderField = React.forwardRef((props, _ref) => ( )); -export const RequestBodyField = React.forwardRef((props, _ref) => ( - -)); +export const RequestBodyField = React.forwardRef( + (props, _ref) => +); -export const ResponseBodyIndexField = React.forwardRef( +export const ResponseBodyIndexField = React.forwardRef( (props, _ref) => ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx index ed1208a80173c..9c49b0989b5de 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/form_config.tsx @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; import { ConfigKey, FormMonitorType, FieldMeta } from '../types'; - +import { AlertConfigKey } from '../constants'; import { FIELD } from './field_config'; -const DEFAULT_DATA_OPTIONS = { +const DEFAULT_DATA_OPTIONS = (readOnly: boolean) => ({ title: i18n.translate('xpack.synthetics.monitorConfig.section.dataOptions.title', { defaultMessage: 'Data options', }), @@ -18,13 +18,13 @@ const DEFAULT_DATA_OPTIONS = { defaultMessage: 'Configure data options to add context to the data coming from your monitors.', }), components: [ - FIELD[ConfigKey.TAGS], - FIELD[ConfigKey.APM_SERVICE_NAME], - FIELD[ConfigKey.NAMESPACE], + FIELD(readOnly)[ConfigKey.TAGS], + FIELD(readOnly)[ConfigKey.APM_SERVICE_NAME], + FIELD(readOnly)[ConfigKey.NAMESPACE], ], -}; +}); -const HTTP_ADVANCED = { +const HTTP_ADVANCED = (readOnly: boolean) => ({ requestConfig: { title: i18n.translate('xpack.synthetics.monitorConfig.section.requestConfiguration.title', { defaultMessage: 'Request configuration', @@ -37,12 +37,12 @@ const HTTP_ADVANCED = { } ), components: [ - FIELD[ConfigKey.USERNAME], - FIELD[ConfigKey.PASSWORD], - FIELD[ConfigKey.PROXY_URL], - FIELD[ConfigKey.REQUEST_METHOD_CHECK], - FIELD[ConfigKey.REQUEST_HEADERS_CHECK], - FIELD[ConfigKey.REQUEST_BODY_CHECK], + FIELD(readOnly)[ConfigKey.USERNAME], + FIELD(readOnly)[ConfigKey.PASSWORD], + FIELD(readOnly)[ConfigKey.PROXY_URL], + FIELD(readOnly)[ConfigKey.REQUEST_METHOD_CHECK], + FIELD(readOnly)[ConfigKey.REQUEST_HEADERS_CHECK], + FIELD(readOnly)[ConfigKey.REQUEST_BODY_CHECK], ], }, responseConfig: { @@ -55,7 +55,10 @@ const HTTP_ADVANCED = { defaultMessage: 'Control the indexing of the HTTP response contents.', } ), - components: [FIELD[ConfigKey.RESPONSE_HEADERS_INDEX], FIELD[ConfigKey.RESPONSE_BODY_INDEX]], + components: [ + FIELD(readOnly)[ConfigKey.RESPONSE_HEADERS_INDEX], + FIELD(readOnly)[ConfigKey.RESPONSE_BODY_INDEX], + ], }, responseChecks: { title: i18n.translate('xpack.synthetics.monitorConfig.section.responseChecks.title', { @@ -68,15 +71,15 @@ const HTTP_ADVANCED = { } ), components: [ - FIELD[ConfigKey.RESPONSE_STATUS_CHECK], - FIELD[ConfigKey.RESPONSE_HEADERS_CHECK], - FIELD[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE], - FIELD[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE], + FIELD(readOnly)[ConfigKey.RESPONSE_STATUS_CHECK], + FIELD(readOnly)[ConfigKey.RESPONSE_HEADERS_CHECK], + FIELD(readOnly)[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE], + FIELD(readOnly)[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE], ], }, -}; +}); -export const TCP_ADVANCED = { +export const TCP_ADVANCED = (readOnly: boolean) => ({ requestConfig: { title: i18n.translate('xpack.synthetics.monitorConfig.section.requestConfigTCP.title', { defaultMessage: 'Request configuration', @@ -87,7 +90,10 @@ export const TCP_ADVANCED = { defaultMessage: 'Configure the payload sent to the remote host.', } ), - components: [FIELD[`${ConfigKey.PROXY_URL}__tcp`], FIELD[ConfigKey.REQUEST_SEND_CHECK]], + components: [ + FIELD(readOnly)[`${ConfigKey.PROXY_URL}__tcp`], + FIELD(readOnly)[ConfigKey.REQUEST_SEND_CHECK], + ], }, responseChecks: { title: i18n.translate('xpack.synthetics.monitorConfig.section.responseChecksTCP.title', { @@ -99,11 +105,11 @@ export const TCP_ADVANCED = { defaultMessage: 'Configure the expected response from the remote host.', } ), - components: [FIELD[ConfigKey.RESPONSE_RECEIVE_CHECK]], + components: [FIELD(readOnly)[ConfigKey.RESPONSE_RECEIVE_CHECK]], }, -}; +}); -export const BROWSER_ADVANCED = [ +export const BROWSER_ADVANCED = (readOnly: boolean) => [ { title: i18n.translate('xpack.synthetics.monitorConfig.section.syntAgentOptions.title', { defaultMessage: 'Synthetics agent options', @@ -115,9 +121,9 @@ export const BROWSER_ADVANCED = [ } ), components: [ - FIELD[ConfigKey.IGNORE_HTTPS_ERRORS], - FIELD[ConfigKey.SYNTHETICS_ARGS], - FIELD[ConfigKey.PLAYWRIGHT_OPTIONS], + FIELD(readOnly)[ConfigKey.IGNORE_HTTPS_ERRORS], + FIELD(readOnly)[ConfigKey.SYNTHETICS_ARGS], + FIELD(readOnly)[ConfigKey.PLAYWRIGHT_OPTIONS], ], }, ]; @@ -125,21 +131,21 @@ export const BROWSER_ADVANCED = [ interface AdvancedFieldGroup { title: string; description: string; - components: FieldMeta[]; + components: Array>; } type FieldConfig = Record< FormMonitorType, { - step1: FieldMeta[]; - step2: FieldMeta[]; - step3?: FieldMeta[]; - scriptEdit?: FieldMeta[]; + step1: Array>; + step2: Array>; + step3?: Array>; + scriptEdit?: Array>; advanced?: AdvancedFieldGroup[]; } >; -const TLS_OPTIONS = { +const TLS_OPTIONS = (readOnly: boolean): AdvancedFieldGroup => ({ title: i18n.translate('xpack.synthetics.monitorConfig.section.tlsOptions.title', { defaultMessage: 'TLS options', }), @@ -148,116 +154,116 @@ const TLS_OPTIONS = { 'Configure TLS options, including verification mode, certificate authorities, and client certificates.', }), components: [ - FIELD.isTLSEnabled, - FIELD[ConfigKey.TLS_VERIFICATION_MODE], - FIELD[ConfigKey.TLS_VERSION], - FIELD[ConfigKey.TLS_CERTIFICATE_AUTHORITIES], - FIELD[ConfigKey.TLS_CERTIFICATE], - FIELD[ConfigKey.TLS_KEY], - FIELD[ConfigKey.TLS_KEY_PASSPHRASE], + FIELD(readOnly).isTLSEnabled, + FIELD(readOnly)[ConfigKey.TLS_VERIFICATION_MODE], + FIELD(readOnly)[ConfigKey.TLS_VERSION], + FIELD(readOnly)[ConfigKey.TLS_CERTIFICATE_AUTHORITIES], + FIELD(readOnly)[ConfigKey.TLS_CERTIFICATE], + FIELD(readOnly)[ConfigKey.TLS_KEY], + FIELD(readOnly)[ConfigKey.TLS_KEY_PASSPHRASE], ], -}; +}); -export const FORM_CONFIG: FieldConfig = { +export const FORM_CONFIG = (readOnly: boolean): FieldConfig => ({ [FormMonitorType.HTTP]: { - step1: [FIELD[ConfigKey.FORM_MONITOR_TYPE]], + step1: [FIELD(readOnly)[ConfigKey.FORM_MONITOR_TYPE]], step2: [ - FIELD[`${ConfigKey.URLS}__http`], - FIELD[ConfigKey.NAME], - FIELD[ConfigKey.LOCATIONS], - FIELD[ConfigKey.SCHEDULE], - FIELD[ConfigKey.MAX_REDIRECTS], - FIELD[ConfigKey.TIMEOUT], - FIELD[ConfigKey.ENABLED], - FIELD[ConfigKey.ALERT_CONFIG], + FIELD(readOnly)[`${ConfigKey.URLS}__http`], + FIELD(readOnly)[ConfigKey.NAME], + FIELD(readOnly)[ConfigKey.LOCATIONS], + FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], + FIELD(readOnly)[ConfigKey.MAX_REDIRECTS], + FIELD(readOnly)[ConfigKey.TIMEOUT], + FIELD(readOnly)[ConfigKey.ENABLED], + FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], advanced: [ - DEFAULT_DATA_OPTIONS, - HTTP_ADVANCED.requestConfig, - HTTP_ADVANCED.responseConfig, - HTTP_ADVANCED.responseChecks, - TLS_OPTIONS, + DEFAULT_DATA_OPTIONS(readOnly), + HTTP_ADVANCED(readOnly).requestConfig, + HTTP_ADVANCED(readOnly).responseConfig, + HTTP_ADVANCED(readOnly).responseChecks, + TLS_OPTIONS(readOnly), ], }, [FormMonitorType.TCP]: { - step1: [FIELD[ConfigKey.FORM_MONITOR_TYPE]], + step1: [FIELD(readOnly)[ConfigKey.FORM_MONITOR_TYPE]], step2: [ - FIELD[`${ConfigKey.HOSTS}__tcp`], - FIELD[ConfigKey.NAME], - FIELD[ConfigKey.LOCATIONS], - FIELD[ConfigKey.SCHEDULE], - FIELD[ConfigKey.TIMEOUT], - FIELD[ConfigKey.ENABLED], - FIELD[ConfigKey.ALERT_CONFIG], + FIELD(readOnly)[`${ConfigKey.HOSTS}__tcp`], + FIELD(readOnly)[ConfigKey.NAME], + FIELD(readOnly)[ConfigKey.LOCATIONS], + FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], + FIELD(readOnly)[ConfigKey.TIMEOUT], + FIELD(readOnly)[ConfigKey.ENABLED], + FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], advanced: [ - DEFAULT_DATA_OPTIONS, - TCP_ADVANCED.requestConfig, - TCP_ADVANCED.responseChecks, - TLS_OPTIONS, + DEFAULT_DATA_OPTIONS(readOnly), + TCP_ADVANCED(readOnly).requestConfig, + TCP_ADVANCED(readOnly).responseChecks, + TLS_OPTIONS(readOnly), ], }, [FormMonitorType.MULTISTEP]: { - step1: [FIELD[ConfigKey.FORM_MONITOR_TYPE]], + step1: [FIELD(readOnly)[ConfigKey.FORM_MONITOR_TYPE]], step2: [ - FIELD[ConfigKey.NAME], - FIELD[ConfigKey.LOCATIONS], - FIELD[ConfigKey.SCHEDULE], - FIELD[ConfigKey.THROTTLING_CONFIG], - FIELD[ConfigKey.ENABLED], - FIELD[ConfigKey.ALERT_CONFIG], + FIELD(readOnly)[ConfigKey.NAME], + FIELD(readOnly)[ConfigKey.LOCATIONS], + FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], + FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG], + FIELD(readOnly)[ConfigKey.ENABLED], + FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], - step3: [FIELD[ConfigKey.SOURCE_INLINE], FIELD[ConfigKey.PARAMS]], - scriptEdit: [FIELD[ConfigKey.SOURCE_INLINE]], + step3: [FIELD(readOnly)['source.inline'], FIELD(readOnly)[ConfigKey.PARAMS]], + scriptEdit: [FIELD(readOnly)['source.inline']], advanced: [ { - ...DEFAULT_DATA_OPTIONS, + ...DEFAULT_DATA_OPTIONS(readOnly), components: [ - FIELD[ConfigKey.TAGS], - FIELD[ConfigKey.APM_SERVICE_NAME], - FIELD[ConfigKey.SCREENSHOTS], - FIELD[ConfigKey.NAMESPACE], + FIELD(readOnly)[ConfigKey.TAGS], + FIELD(readOnly)[ConfigKey.APM_SERVICE_NAME], + FIELD(readOnly)[ConfigKey.SCREENSHOTS], + FIELD(readOnly)[ConfigKey.NAMESPACE], ], }, - ...BROWSER_ADVANCED, + ...BROWSER_ADVANCED(readOnly), ], }, [FormMonitorType.SINGLE]: { - step1: [FIELD[ConfigKey.FORM_MONITOR_TYPE]], + step1: [FIELD(readOnly)[ConfigKey.FORM_MONITOR_TYPE]], step2: [ - FIELD[`${ConfigKey.URLS}__single`], - FIELD[ConfigKey.NAME], - FIELD[ConfigKey.TEXT_ASSERTION], - FIELD[ConfigKey.LOCATIONS], - FIELD[ConfigKey.SCHEDULE], - FIELD[ConfigKey.THROTTLING_CONFIG], - FIELD[ConfigKey.ENABLED], - FIELD[ConfigKey.ALERT_CONFIG], + FIELD(readOnly)[`${ConfigKey.URLS}__single`], + FIELD(readOnly)[ConfigKey.NAME], + FIELD(readOnly)[ConfigKey.TEXT_ASSERTION], + FIELD(readOnly)[ConfigKey.LOCATIONS], + FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], + FIELD(readOnly)[ConfigKey.THROTTLING_CONFIG], + FIELD(readOnly)[ConfigKey.ENABLED], + FIELD(readOnly)[AlertConfigKey.STATUS_ENABLED], ], advanced: [ { - ...DEFAULT_DATA_OPTIONS, + ...DEFAULT_DATA_OPTIONS(readOnly), components: [ - FIELD[ConfigKey.TAGS], - FIELD[ConfigKey.APM_SERVICE_NAME], - FIELD[ConfigKey.SCREENSHOTS], - FIELD[ConfigKey.NAMESPACE], + FIELD(readOnly)[ConfigKey.TAGS], + FIELD(readOnly)[ConfigKey.APM_SERVICE_NAME], + FIELD(readOnly)[ConfigKey.SCREENSHOTS], + FIELD(readOnly)[ConfigKey.NAMESPACE], ], }, - ...BROWSER_ADVANCED, + ...BROWSER_ADVANCED(readOnly), ], }, [FormMonitorType.ICMP]: { - step1: [FIELD[ConfigKey.FORM_MONITOR_TYPE]], + step1: [FIELD(readOnly)[ConfigKey.FORM_MONITOR_TYPE]], step2: [ - FIELD[`${ConfigKey.HOSTS}__icmp`], - FIELD[ConfigKey.NAME], - FIELD[ConfigKey.LOCATIONS], - FIELD[ConfigKey.SCHEDULE], - FIELD[ConfigKey.WAIT], - FIELD[ConfigKey.TIMEOUT], - FIELD[ConfigKey.ENABLED], + FIELD(readOnly)[`${ConfigKey.HOSTS}__icmp`], + FIELD(readOnly)[ConfigKey.NAME], + FIELD(readOnly)[ConfigKey.LOCATIONS], + FIELD(readOnly)[`${ConfigKey.SCHEDULE}.number`], + FIELD(readOnly)[ConfigKey.WAIT], + FIELD(readOnly)[ConfigKey.TIMEOUT], + FIELD(readOnly)[ConfigKey.ENABLED], ], - advanced: [DEFAULT_DATA_OPTIONS], + advanced: [DEFAULT_DATA_OPTIONS(readOnly)], }, -}; +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx index c284df9de90e7..a534438b7df0d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { format } from './formatter'; +import { format, READ_ONLY_FIELDS } from './formatter'; import { DataStream } from '../../../../../../common/runtime_types'; import { DEFAULT_FIELDS } from '../../../../../../common/constants/monitor_defaults'; @@ -358,4 +358,13 @@ describe('format', () => { id: '', }); }); + + it('handles read only', () => { + expect(format(formValues, true)).toEqual( + READ_ONLY_FIELDS.reduce>((acc, key) => { + acc[key] = formValues[key]; + return acc; + }, {}) + ); + }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.ts index 80afdbb57b73b..1ce3d1258e5fa 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { get } from 'lodash'; +import { get, pick } from 'lodash'; import { ConfigKey, DataStream, FormMonitorType, MonitorFields } from '../types'; import { DEFAULT_FIELDS } from '../constants'; @@ -20,7 +20,9 @@ export const formatter = (fields: Record) => { return monitorFields as MonitorFields; }; -export const format = (fields: Record) => { +export const READ_ONLY_FIELDS = [ConfigKey.ENABLED]; + +export const format = (fields: Record, readOnly: boolean = false) => { const formattedFields = formatter(fields) as MonitorFields; const formattedMap = { [FormMonitorType.SINGLE]: { @@ -38,7 +40,7 @@ export const format = (fields: Record) => { [ConfigKey.METADATA]: { script_source: { is_generated_script: get(fields, 'source.inline.type') === 'recorder' ? true : false, - file_name: get(fields, 'source.inline.fileName'), + file_name: get(fields, 'source.inline.fileName') || '', }, }, [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, @@ -62,5 +64,6 @@ export const format = (fields: Record) => { [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.ICMP, }, }; - return formattedMap[fields[ConfigKey.FORM_MONITOR_TYPE] as FormMonitorType]; + const formFields = formattedMap[fields[ConfigKey.FORM_MONITOR_TYPE] as FormMonitorType]; + return readOnly ? pick(formFields, READ_ONLY_FIELDS) : formFields; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx index 8abd76971879e..64cfaae171a26 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/index.tsx @@ -14,11 +14,11 @@ import { getDefaultFormFields, formatDefaultFormValues } from './defaults'; import { ActionBar } from './submit'; import { Disclaimer } from './disclaimer'; -export const MonitorForm: React.FC<{ defaultValues?: SyntheticsMonitor; space?: string }> = ({ - children, - defaultValues, - space, -}) => { +export const MonitorForm: React.FC<{ + defaultValues?: SyntheticsMonitor; + space?: string; + readOnly?: boolean; +}> = ({ children, defaultValues, space, readOnly = false }) => { const methods = useFormWrapped({ mode: 'onSubmit', reValidateMode: 'onChange', @@ -43,7 +43,7 @@ export const MonitorForm: React.FC<{ defaultValues?: SyntheticsMonitor; space?: > {children} - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx index 93a7a9f0233cf..ba29b5ca3fd6d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/submit.tsx @@ -19,7 +19,7 @@ import { format } from './formatter'; import { MONITORS_ROUTE } from '../../../../../../common/constants'; -export const ActionBar = () => { +export const ActionBar = ({ readOnly = false }: { readOnly: boolean }) => { const { monitorId } = useParams<{ monitorId: string }>(); const history = useHistory(); const { @@ -37,7 +37,7 @@ export const ActionBar = () => { const formSubmitter = (formData: Record) => { if (!Object.keys(errors).length) { - setMonitorData(format(formData)); + setMonitorData(format(formData, readOnly)); } }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx index 4538a0ef96b68..38865ae16cdca 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/monitor_edit_page.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom'; import { useDispatch } from 'react-redux'; import { useTrackPageview, useFetcher } from '@kbn/observability-plugin/public'; import { LoadingState } from '../monitors_page/overview/overview/monitor_detail_flyout'; -import { ConfigKey } from '../../../../../common/runtime_types'; +import { ConfigKey, SourceType } from '../../../../../common/runtime_types'; import { getServiceLocations } from '../../state'; import { ServiceAllowedWrapper } from '../common/wrappers/service_allowed_wrapper'; import { MonitorSteps } from './steps'; @@ -35,9 +35,17 @@ const MonitorEditPage: React.FC = () => { return getMonitorAPI({ id: monitorId }); }, []); + const isReadOnly = data?.attributes[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; + const projectId = data?.attributes[ConfigKey.PROJECT_ID]; + return data && !loading && !error ? ( - - + + { const { watch } = useFormContext(); const [type]: [FormMonitorType] = watch([ConfigKey.FORM_MONITOR_TYPE]); @@ -25,6 +30,12 @@ export const MonitorSteps = ({ return ( <> + {readOnly ? ( + <> + + + + ) : null} {isEditFlow ? ( steps.map((step) => (
@@ -41,7 +52,7 @@ export const MonitorSteps = ({ ) : ( )} - + ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.tsx new file mode 100644 index 0000000000000..6a3d2fda361f0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/read_only_callout.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 from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const ReadOnlyCallout = ({ projectId }: { projectId?: string }) => { + return ( + + } + iconType="document" + > +

+ {projectId} }} + /> +

+
+ ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx index 83463610c4a4e..7a1a95bdd57c1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_config.tsx @@ -29,7 +29,7 @@ const MONITOR_TYPE_STEP: Step = { /> ), }; -const MONITOR_DETAILS_STEP: Step = { +const MONITOR_DETAILS_STEP = (readOnly: boolean = false): Step => ({ title: i18n.translate('xpack.synthetics.monitorConfig.monitorDetailsStep.title', { defaultMessage: 'Monitor details', }), @@ -43,9 +43,10 @@ const MONITOR_DETAILS_STEP: Step = {

} stepKey="step2" + readOnly={readOnly} /> ), -}; +}); const SCRIPT_RECORDER_BTNS = ( @@ -104,7 +105,7 @@ const MONITOR_SCRIPT_STEP: Step = { ), }; -const MONITOR_SCRIPT_STEP_EDIT: Step = { +const MONITOR_SCRIPT_STEP_EDIT = (readOnly: boolean = false): Step => ({ title: i18n.translate('xpack.synthetics.monitorConfig.monitorScriptEditStep.title', { defaultMessage: 'Monitor script', }), @@ -113,41 +114,50 @@ const MONITOR_SCRIPT_STEP_EDIT: Step = { description={ <>

- - - - ), - }} - /> + {readOnly ? ( + + ) : ( + + + + ), + }} + /> + )}

- {SCRIPT_RECORDER_BTNS} + {readOnly ? null : SCRIPT_RECORDER_BTNS} } stepKey="scriptEdit" + readOnly={readOnly} + descriptionOnly={readOnly} /> ), -}; +}); export const ADD_MONITOR_STEPS: StepMap = { - [FormMonitorType.MULTISTEP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP, MONITOR_SCRIPT_STEP], - [FormMonitorType.SINGLE]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP], - [FormMonitorType.HTTP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP], - [FormMonitorType.ICMP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP], - [FormMonitorType.TCP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP], + [FormMonitorType.MULTISTEP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP(), MONITOR_SCRIPT_STEP], + [FormMonitorType.SINGLE]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP()], + [FormMonitorType.HTTP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP()], + [FormMonitorType.ICMP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP()], + [FormMonitorType.TCP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP()], }; -export const EDIT_MONITOR_STEPS: StepMap = { - [FormMonitorType.MULTISTEP]: [MONITOR_SCRIPT_STEP_EDIT, MONITOR_DETAILS_STEP], - [FormMonitorType.SINGLE]: [MONITOR_DETAILS_STEP], - [FormMonitorType.HTTP]: [MONITOR_DETAILS_STEP], - [FormMonitorType.ICMP]: [MONITOR_DETAILS_STEP], - [FormMonitorType.TCP]: [MONITOR_DETAILS_STEP], -}; +export const EDIT_MONITOR_STEPS = (readOnly: boolean): StepMap => ({ + [FormMonitorType.MULTISTEP]: [MONITOR_SCRIPT_STEP_EDIT(readOnly), MONITOR_DETAILS_STEP(readOnly)], + [FormMonitorType.SINGLE]: [MONITOR_DETAILS_STEP(readOnly)], + [FormMonitorType.HTTP]: [MONITOR_DETAILS_STEP(readOnly)], + [FormMonitorType.ICMP]: [MONITOR_DETAILS_STEP(readOnly)], + [FormMonitorType.TCP]: [MONITOR_DETAILS_STEP(readOnly)], +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx index 2ac9583185913..f8d840baa6e41 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx @@ -5,7 +5,8 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; +import { EuiText } from '@elastic/eui'; import { useFormContext, FieldError } from 'react-hook-form'; import { Step } from './step'; import { FORM_CONFIG } from '../form/form_config'; @@ -15,9 +16,13 @@ import { ConfigKey, FormMonitorType, StepKey } from '../types'; export const StepFields = ({ description, stepKey, + readOnly = false, + descriptionOnly = false, }: { description: React.ReactNode; stepKey: StepKey; + readOnly?: boolean; + descriptionOnly?: boolean; }) => { const { watch, @@ -25,9 +30,15 @@ export const StepFields = ({ } = useFormContext(); const [type]: [FormMonitorType] = watch([ConfigKey.FORM_MONITOR_TYPE]); - return ( + const formConfig = useMemo(() => { + return FORM_CONFIG(readOnly)[type]; + }, [readOnly, type]); + + return descriptionOnly ? ( + {description} + ) : ( - {FORM_CONFIG[type][stepKey]?.map((field) => { + {formConfig[stepKey]?.map((field) => { return ( ; export * from '../../../../../common/runtime_types/monitor_management'; export * from '../../../../../common/types/monitor_validation'; -export interface FieldMeta { - fieldKey: string; +export interface FormLocation { + id: string; + isServiceManaged: boolean; + label: string; +} +export type FormConfig = MonitorFields & { + isTLSEnabled: boolean; + ['schedule.number']: string; + ['source.inline']: string; + [AlertConfigKey.STATUS_ENABLED]: boolean; + [ConfigKey.LOCATIONS]: FormLocation[]; + + /* Dot notiation keys must have a type configuration both for their flattened and nested + * variation in order for types to register for react hook form. For example, `AlertConfigKey.STATUS_ENABLED` + * must be defined both as `alert.config.enabled: boolean` and `alert: { config: { enabled: boolean } }` */ + alert: { + status: { + enabled: boolean; + }; + }; + ssl: { + supported_protocols: MonitorFields[ConfigKey.TLS_VERSION]; + }; +}; + +export interface FieldMeta { + fieldKey: keyof FormConfig; component: React.ComponentType; label?: string; ariaLabel?: string; helpText?: string | React.ReactNode; props?: (params: { - field?: ControllerRenderProps; - formState: FormState; - setValue: UseFormReturn['setValue']; - reset: UseFormReturn['reset']; - locations: ServiceLocations; + field?: ControllerRenderProps; + formState: FormState; + setValue: UseFormReturn['setValue']; + reset: UseFormReturn['reset']; + locations: Array; dependencies: unknown[]; - dependenciesFieldMeta: Record; + dependenciesFieldMeta: Record; space?: string; isEdit?: boolean; }) => Record; @@ -61,8 +88,58 @@ export interface FieldMeta { event: React.ChangeEvent, formOnChange: (event: React.ChangeEvent) => void ) => void; - showWhen?: [string, any]; // show field when another field equals an arbitrary value + showWhen?: [keyof FormConfig, any]; // show field when another field equals an arbitrary value validation?: (dependencies: unknown[]) => Parameters[1]; error?: React.ReactNode; - dependencies?: string[]; // fields that another field may depend for or validation. Values are passed to the validation function + dependencies?: Array; // fields that another field may depend for or validation. Values are passed to the validation function +} + +export interface FieldMap { + [ConfigKey.FORM_MONITOR_TYPE]: FieldMeta; + [`urls__single`]: FieldMeta; + [`urls__http`]: FieldMeta; + [`hosts__tcp`]: FieldMeta; + [`hosts__icmp`]: FieldMeta; + [ConfigKey.NAME]: FieldMeta; + ['schedule.number']: FieldMeta; + [ConfigKey.TAGS]: FieldMeta; + [ConfigKey.TIMEOUT]: FieldMeta; + [ConfigKey.APM_SERVICE_NAME]: FieldMeta; + [ConfigKey.LOCATIONS]: FieldMeta; + ['isTLSEnabled']: FieldMeta<'isTLSEnabled'>; + [ConfigKey.TLS_VERSION]: FieldMeta; + [ConfigKey.TLS_VERIFICATION_MODE]: FieldMeta; + [ConfigKey.TLS_CERTIFICATE]: FieldMeta; + [ConfigKey.TLS_CERTIFICATE_AUTHORITIES]: FieldMeta; + [ConfigKey.TLS_KEY]: FieldMeta; + [ConfigKey.TLS_KEY_PASSPHRASE]: FieldMeta; + [ConfigKey.SCREENSHOTS]: FieldMeta; + [ConfigKey.ENABLED]: FieldMeta; + [AlertConfigKey.STATUS_ENABLED]: FieldMeta; + [ConfigKey.NAMESPACE]: FieldMeta; + [ConfigKey.TIMEOUT]: FieldMeta; + [ConfigKey.MAX_REDIRECTS]: FieldMeta; + [ConfigKey.WAIT]: FieldMeta; + [ConfigKey.USERNAME]: FieldMeta; + [ConfigKey.PASSWORD]: FieldMeta; + [ConfigKey.PROXY_URL]: FieldMeta; + ['proxy_url__tcp']: FieldMeta; + [ConfigKey.REQUEST_METHOD_CHECK]: FieldMeta; + [ConfigKey.REQUEST_HEADERS_CHECK]: FieldMeta; + [ConfigKey.REQUEST_BODY_CHECK]: FieldMeta; + [ConfigKey.RESPONSE_HEADERS_INDEX]: FieldMeta; + [ConfigKey.RESPONSE_BODY_INDEX]: FieldMeta; + [ConfigKey.RESPONSE_STATUS_CHECK]: FieldMeta; + [ConfigKey.RESPONSE_HEADERS_CHECK]: FieldMeta; + [ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: FieldMeta; + [ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: FieldMeta; + [ConfigKey.RESPONSE_RECEIVE_CHECK]: FieldMeta; + [ConfigKey.REQUEST_SEND_CHECK]: FieldMeta; + ['source.inline']: FieldMeta; + [ConfigKey.TEXT_ASSERTION]: FieldMeta; + [ConfigKey.THROTTLING_CONFIG]: FieldMeta; + [ConfigKey.PARAMS]: FieldMeta; + [ConfigKey.PLAYWRIGHT_OPTIONS]: FieldMeta; + [ConfigKey.SYNTHETICS_ARGS]: FieldMeta; + [ConfigKey.IGNORE_HTTPS_ERRORS]: FieldMeta; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx index bd3d45a0a59f0..5901711986419 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_journey_steps.tsx @@ -16,11 +16,18 @@ import { selectBrowserJourneyLoading, } from '../../../state'; -export const useJourneySteps = (checkGroup?: string, lastRefresh?: number) => { - const { stepIndex, checkGroupId: urlCheckGroup } = useParams<{ +export const useJourneySteps = ( + checkGroup?: string, + lastRefresh?: number, + stepIndexArg?: number +) => { + const { stepIndex: stepIndexUrl, checkGroupId: urlCheckGroup } = useParams<{ stepIndex: string; checkGroupId: string; }>(); + + const stepIndex = stepIndexArg ?? stepIndexUrl; + const checkGroupId = checkGroup ?? urlCheckGroup; const journeyData = useSelector(selectBrowserJourney(checkGroupId)); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx index c493f863d5bc5..fa279a4d64a4c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx @@ -6,11 +6,21 @@ */ import React from 'react'; -import { useSelectedMonitor } from './hooks/use_selected_monitor'; +import { Redirect, useParams } from 'react-router-dom'; + +import { MONITOR_NOT_FOUND_ROUTE } from '../../../../../common/constants'; import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs'; +import { useSelectedMonitor } from './hooks/use_selected_monitor'; export const MonitorDetailsPage: React.FC<{ children: React.ReactElement }> = ({ children }) => { - const { monitor } = useSelectedMonitor(); + const { monitor, error } = useSelectedMonitor(); + + const { monitorId } = useParams<{ monitorId: string }>(); + useMonitorListBreadcrumbs(monitor ? [{ text: monitor?.name ?? '' }] : []); + + if (error?.body.statusCode === 404) { + return ; + } return children; }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_not_found_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_not_found_page.tsx new file mode 100644 index 0000000000000..2d05f24d9d51d --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_not_found_page.tsx @@ -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. + */ + +import React from 'react'; +import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useParams } from 'react-router-dom'; +import { CreateMonitorButton } from '../monitors_page/create_monitor_button'; +import { PLUGIN } from '../../../../../common/constants/plugin'; +import { ClientPluginsStart } from '../../../../plugin'; + +export const MonitorNotFoundPage: React.FC = () => { + const { application } = useKibana().services; + const { monitorId } = useParams<{ monitorId: string }>(); + + return ( + {monitorId} }} + /> + } + actions={[ + , + { + application.navigateToApp(PLUGIN.SYNTHETICS_PLUGIN_ID); + }} + > + {i18n.translate('xpack.synthetics.routes.createNewMonitor', { + defaultMessage: 'Go to Home', + })} + , + ]} + /> + ); +}; + +const NOT_FOUND_TITLE = i18n.translate('xpack.synthetics.prompt.errors.notFound.title', { + defaultMessage: 'Monitor not found', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx index 9c4249517b64f..48a8ec5962042 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx @@ -86,7 +86,7 @@ export const LastTestRunComponent = ({ return ( - {!loading && latestPing?.error ? ( + {!(loading && !latestPing) && latestPing?.error ? ( ) : ( @@ -163,7 +163,7 @@ const PanelHeader = ({ ); - if (loading) { + if (loading && !latestPing) { return ( <> diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx index 8f2c7f36dfcb4..8acf81c10e778 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/route_config.tsx @@ -10,6 +10,7 @@ import React from 'react'; import { useHistory, useRouteMatch } from 'react-router-dom'; import { EuiIcon, EuiPageHeaderProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { MonitorNotFoundPage } from './monitor_not_found_page'; import { MonitorDetailsPageTitle } from './monitor_details_page_title'; import { EditMonitorLink } from '../common/links/edit_monitor'; import { RunTestManually } from './run_test_manually'; @@ -23,8 +24,9 @@ import { MonitorDetailsPage } from './monitor_details_page'; import { MONITOR_ERRORS_ROUTE, MONITOR_HISTORY_ROUTE, + MONITOR_NOT_FOUND_ROUTE, MONITOR_ROUTE, - OVERVIEW_ROUTE, + MONITORS_ROUTE, } from '../../../../../common/constants'; import { RouteProps } from '../../routes'; @@ -76,9 +78,36 @@ export const getMonitorDetailsRoute = ( dataTestSubj: 'syntheticsMonitorHistoryPage', pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'errors'), }, + { + title: i18n.translate('xpack.synthetics.monitorNotFound.title', { + defaultMessage: 'Synthetics Monitor Not Found | {baseTitle}', + values: { baseTitle }, + }), + path: MONITOR_NOT_FOUND_ROUTE, + component: () => , + dataTestSubj: 'syntheticsMonitorNotFoundPage', + pageHeader: { + breadcrumbs: [getMonitorsBreadcrumb(syntheticsPath)], + }, + }, ]; }; +const getMonitorsBreadcrumb = (syntheticsPath: string) => ({ + text: ( + <> + {' '} + + + ), + color: 'primary' as const, + 'aria-current': false, + href: `${syntheticsPath}${MONITORS_ROUTE}`, +}); + const getMonitorSummaryHeader = ( history: ReturnType, syntheticsPath: string, @@ -96,22 +125,7 @@ const getMonitorSummaryHeader = ( return { pageTitle: , - breadcrumbs: [ - { - text: ( - <> - {' '} - - - ), - color: 'primary', - 'aria-current': false, - href: `${syntheticsPath}${OVERVIEW_ROUTE}`, - }, - ], + breadcrumbs: [getMonitorsBreadcrumb(syntheticsPath)], rightSideItems: [ , , diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_button.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_button.tsx new file mode 100644 index 0000000000000..65cc56a473cd1 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_button.tsx @@ -0,0 +1,55 @@ +/* + * 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 } from 'react'; +import { FieldValueSelection } from '@kbn/observability-plugin/public'; +import { + getSyntheticsFilterDisplayValues, + SyntheticsMonitorFilterItem, + valueToLabelWithEmptyCount, +} from './filter_fields'; +import { useGetUrlParams } from '../../../../hooks'; +import { useMonitorFiltersState } from './use_filters'; + +export const FilterButton = ({ + filter, + handleFilterChange, +}: { + filter: SyntheticsMonitorFilterItem; + handleFilterChange: ReturnType['handleFilterChange']; +}) => { + const { label, values, field } = filter; + + const [query, setQuery] = useState(''); + + const urlParams = useGetUrlParams(); + + // Transform the values to readable labels (if any) so that selected values are checked on filter dropdown + const selectedValueLabels = getSyntheticsFilterDisplayValues( + (urlParams[field] || []).map(valueToLabelWithEmptyCount), + field, + [] + ).map(({ label: selectedValueLabel }) => selectedValueLabel); + + return ( + str.toLowerCase().includes(query.toLowerCase())) + : values + } + setQuery={setQuery} + onChange={(selectedValues) => handleFilterChange(field, selectedValues)} + allowExclusions={false} + loading={false} + asFilterButton={true} + /> + ); +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_fields.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_fields.ts new file mode 100644 index 0000000000000..7753102fb3fdf --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_fields.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 { i18n } from '@kbn/i18n'; +import { invert } from 'lodash'; +import { DataStream, ServiceLocations } from '../../../../../../../common/runtime_types'; +import { MonitorFilterState } from '../../../../state'; + +export type SyntheticsMonitorFilterField = keyof MonitorFilterState; + +export interface LabelWithCountValue { + label: string; + count: number; +} + +export interface SyntheticsMonitorFilterItem { + label: string; + values: LabelWithCountValue[]; + field: SyntheticsMonitorFilterField; +} + +export function getMonitorFilterFields(): SyntheticsMonitorFilterField[] { + return ['tags', 'locations', 'monitorTypes', 'projects', 'schedules']; +} + +export type SyntheticsMonitorFilterChangeHandler = ( + field: SyntheticsMonitorFilterField, + selectedValues: string[] | undefined +) => void; + +export function getSyntheticsFilterDisplayValues( + values: LabelWithCountValue[], + field: SyntheticsMonitorFilterField, + locations: ServiceLocations +) { + switch (field) { + case 'monitorTypes': + return values.map(({ label, count }: { label: string; count: number }) => ({ + label: monitorTypeKeyLabelMap[label as DataStream] ?? label, + count, + })); + case 'schedules': + return values.map(({ label, count }: { label: string; count: number }) => ({ + label: i18n.translate('xpack.synthetics.monitorFilters.frequencyLabel', { + defaultMessage: `Every {count} minutes`, + values: { count: label }, + }), + count, + })); + case 'locations': + return values.map(({ label, count }) => { + const foundLocation = locations.find( + ({ id: locationId, label: locationLabel }) => + label === locationId || label === locationLabel + ); + return { + label: foundLocation?.label ?? label, + count, + }; + }); + default: + return values; + } +} + +export function getSyntheticsFilterKeyForLabel(value: string, field: SyntheticsMonitorFilterField) { + switch (field) { + case 'monitorTypes': + return invert(monitorTypeKeyLabelMap)[value] ?? value; + case 'schedules': + return (value ?? '').replace(/\D/g, ''); + default: + return value; + } +} + +export const valueToLabelWithEmptyCount = (value: string): LabelWithCountValue => ({ + label: value, + count: 0, +}); + +const monitorTypeKeyLabelMap: Record = { + [DataStream.BROWSER]: 'Journey / Page', + [DataStream.HTTP]: 'HTTP', + [DataStream.TCP]: 'TCP', + [DataStream.ICMP]: 'ICMP', +}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/filter_group.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx similarity index 54% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/filter_group.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx index 904c3c7f18a3f..a7db1094ff262 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/filter_group.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/filter_group.tsx @@ -10,50 +10,64 @@ import { EuiFilterGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useSelector } from 'react-redux'; import { ServiceLocations } from '../../../../../../../common/runtime_types'; -import { useFilters } from './use_filters'; -import { FilterButton } from './filter_button'; import { selectServiceLocationsState } from '../../../../state'; -export interface FilterItem { - label: string; - values: Array<{ label: string; count: number }>; - field: 'tags' | 'status' | 'locations' | 'monitorType'; -} +import { + SyntheticsMonitorFilterItem, + getSyntheticsFilterDisplayValues, + SyntheticsMonitorFilterChangeHandler, +} from './filter_fields'; +import { useFilters } from './use_filters'; +import { FilterButton } from './filter_button'; export const findLocationItem = (query: string, locations: ServiceLocations) => { return locations.find(({ id, label }) => query === id || label === query); }; -export const FilterGroup = () => { +export const FilterGroup = ({ + handleFilterChange, +}: { + handleFilterChange: SyntheticsMonitorFilterChangeHandler; +}) => { const data = useFilters(); const { locations } = useSelector(selectServiceLocationsState); - const filters: FilterItem[] = [ + const filters: SyntheticsMonitorFilterItem[] = [ { label: TYPE_LABEL, - field: 'monitorType', - values: data.types, + field: 'monitorTypes', + values: getSyntheticsFilterDisplayValues(data.monitorTypes, 'monitorTypes', locations), }, { label: LOCATION_LABEL, field: 'locations', - values: data.locations.map(({ label, count }) => ({ - label: findLocationItem(label, locations)?.label ?? label, - count, - })), + values: getSyntheticsFilterDisplayValues(data.locations, 'locations', locations), }, { label: TAGS_LABEL, field: 'tags', - values: data.tags, + values: getSyntheticsFilterDisplayValues(data.tags, 'tags', locations), + }, + { + label: SCHEDULE_LABEL, + field: 'schedules', + values: getSyntheticsFilterDisplayValues(data.schedules, 'schedules', locations), }, ]; + if (data.projects.length > 0) { + filters.push({ + label: PROJECT_LABEL, + field: 'projects', + values: getSyntheticsFilterDisplayValues(data.projects, 'projects', locations), + }); + } + return ( {filters.map((filter, index) => ( - + ))} ); @@ -63,6 +77,10 @@ const TYPE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.filter.typ defaultMessage: `Type`, }); +const PROJECT_LABEL = i18n.translate('xpack.synthetics.monitorManagement.filter.projectLabel', { + defaultMessage: `Project`, +}); + const LOCATION_LABEL = i18n.translate('xpack.synthetics.monitorManagement.filter.locationLabel', { defaultMessage: `Location`, }); @@ -70,3 +88,7 @@ const LOCATION_LABEL = i18n.translate('xpack.synthetics.monitorManagement.filter const TAGS_LABEL = i18n.translate('xpack.synthetics.monitorManagement.filter.tagsLabel', { defaultMessage: `Tags`, }); + +const SCHEDULE_LABEL = i18n.translate('xpack.synthetics.monitorManagement.filter.frequencyLabel', { + defaultMessage: `Frequency`, +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/list_filters.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx similarity index 60% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/list_filters.tsx rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx index 1d693a3f991bd..9e654da2078e3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/list_filters.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/list_filters.tsx @@ -7,17 +7,23 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + import { FilterGroup } from './filter_group'; -import { SearchField } from '../../common/search_field'; +import { SearchField } from '../search_field'; +import { SyntheticsMonitorFilterChangeHandler } from './filter_fields'; -export function ListFilters() { +export function ListFilters({ + handleFilterChange, +}: { + handleFilterChange: SyntheticsMonitorFilterChangeHandler; +}) { return ( - + - + ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts similarity index 65% rename from x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts rename to x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts index 5a345ff88bcce..be231f10e8d84 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.test.ts @@ -13,18 +13,30 @@ describe('useMonitorListFilters', () => { it('returns expected results', () => { const { result } = renderHook(() => useFilters(), { wrapper: WrappedHelper }); - expect(result.current).toStrictEqual({ locations: [], tags: [], types: [] }); + expect(result.current).toStrictEqual({ + locations: [], + tags: [], + monitorTypes: [], + projects: [], + schedules: [], + }); expect(defaultCore.savedObjects.client.find).toHaveBeenCalledWith({ aggs: { locations: { terms: { field: 'synthetics-monitor.attributes.locations.id', size: 10000 }, }, + monitorTypes: { + terms: { field: 'synthetics-monitor.attributes.type.keyword', size: 10000 }, + }, + projects: { + terms: { field: 'synthetics-monitor.attributes.project_id', size: 10000 }, + }, + schedules: { + terms: { field: 'synthetics-monitor.attributes.schedule.number', size: 10000 }, + }, tags: { terms: { field: 'synthetics-monitor.attributes.tags', size: 10000 }, }, - types: { - terms: { field: 'synthetics-monitor.attributes.type.keyword', size: 10000 }, - }, }, perPage: 0, type: 'synthetics-monitor', @@ -40,18 +52,30 @@ describe('useMonitorListFilters', () => { { key: 'Test 2', doc_count: 2 }, ], }, - tags: { + monitorTypes: { buckets: [ { key: 'Test 3', doc_count: 3 }, { key: 'Test 4', doc_count: 4 }, ], }, - types: { + projects: { buckets: [ { key: 'Test 5', doc_count: 5 }, { key: 'Test 6', doc_count: 6 }, ], }, + schedules: { + buckets: [ + { key: 'Test 7', doc_count: 7 }, + { key: 'Test 8', doc_count: 8 }, + ], + }, + tags: { + buckets: [ + { key: 'Test 9', doc_count: 9 }, + { key: 'Test 10', doc_count: 10 }, + ], + }, }, }); @@ -59,7 +83,13 @@ describe('useMonitorListFilters', () => { wrapper: WrappedHelper, }); - expect(result.current).toStrictEqual({ locations: [], tags: [], types: [] }); + expect(result.current).toStrictEqual({ + locations: [], + tags: [], + monitorTypes: [], + projects: [], + schedules: [], + }); await waitForNextUpdate(); @@ -68,14 +98,22 @@ describe('useMonitorListFilters', () => { { label: 'Test 1', count: 1 }, { label: 'Test 2', count: 2 }, ], - tags: [ + monitorTypes: [ { label: 'Test 3', count: 3 }, { label: 'Test 4', count: 4 }, ], - types: [ + projects: [ { label: 'Test 5', count: 5 }, { label: 'Test 6', count: 6 }, ], + schedules: [ + { label: 'Test 7', count: 7 }, + { label: 'Test 8', count: 8 }, + ], + tags: [ + { label: 'Test 9', count: 9 }, + { label: 'Test 10', count: 10 }, + ], }); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts new file mode 100644 index 0000000000000..77b32da98aef0 --- /dev/null +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/monitor_filters/use_filters.ts @@ -0,0 +1,221 @@ +/* + * 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 { useMemo, useEffect, useCallback, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useFetcher } from '@kbn/observability-plugin/public'; + +import { ConfigKey } from '../../../../../../../common/runtime_types'; +import { syntheticsMonitorType } from '../../../../../../../common/types/saved_objects'; +import { + MonitorFilterState, + selectMonitorFiltersAndQueryState, + setOverviewPageStateAction, + updateManagementPageStateAction, +} from '../../../../state'; +import { SyntheticsUrlParams } from '../../../../utils/url_params'; +import { useUrlParams } from '../../../../hooks'; + +import { + SyntheticsMonitorFilterField, + getMonitorFilterFields, + getSyntheticsFilterKeyForLabel, + SyntheticsMonitorFilterChangeHandler, +} from './filter_fields'; + +const aggs = { + monitorTypes: { + terms: { + field: `${syntheticsMonitorType}.attributes.${ConfigKey.MONITOR_TYPE}.keyword`, + size: 10000, + }, + }, + tags: { + terms: { + field: `${syntheticsMonitorType}.attributes.${ConfigKey.TAGS}`, + size: 10000, + }, + }, + locations: { + terms: { + field: `${syntheticsMonitorType}.attributes.${ConfigKey.LOCATIONS}.id`, + size: 10000, + }, + }, + projects: { + terms: { + field: `${syntheticsMonitorType}.attributes.${ConfigKey.PROJECT_ID}`, + size: 10000, + }, + }, + schedules: { + terms: { + field: `${syntheticsMonitorType}.attributes.${ConfigKey.SCHEDULE}.number`, + size: 10000, + }, + }, +}; + +type Buckets = Array<{ + key: string; + doc_count: number; +}>; + +interface AggsResponse { + monitorTypes: { + buckets: Buckets; + }; + locations: { + buckets: Buckets; + }; + tags: { + buckets: Buckets; + }; + projects: { + buckets: Buckets; + }; + schedules: { + buckets: Buckets; + }; +} + +export const useFilters = (): Record< + SyntheticsMonitorFilterField, + Array<{ label: string; count: number }> +> => { + const { savedObjects } = useKibana().services; + + const { data } = useFetcher(async () => { + return savedObjects?.client.find({ + type: syntheticsMonitorType, + perPage: 0, + aggs, + }); + }, []); + + return useMemo(() => { + const { monitorTypes, tags, locations, projects, schedules } = + (data?.aggregations as AggsResponse) ?? {}; + return { + monitorTypes: + monitorTypes?.buckets?.map(({ key, doc_count: count }) => ({ + label: key, + count, + })) ?? [], + tags: + tags?.buckets?.map(({ key, doc_count: count }) => ({ + label: key, + count, + })) ?? [], + locations: + locations?.buckets?.map(({ key, doc_count: count }) => ({ + label: key, + count, + })) ?? [], + projects: + projects?.buckets + ?.filter(({ key }) => key) + .map(({ key, doc_count: count }) => ({ + label: key, + count, + })) ?? [], + schedules: + schedules?.buckets?.map(({ key, doc_count: count }) => ({ + label: key, + count, + })) ?? [], + }; + }, [data]); +}; + +type FilterFieldWithQuery = SyntheticsMonitorFilterField | 'query'; +type FilterStateWithQuery = MonitorFilterState & { query?: string }; + +export function useMonitorFiltersState() { + const [getUrlParams, updateUrlParams] = useUrlParams(); + const urlParams = getUrlParams(); + + const filterFieldsWithQuery: FilterFieldWithQuery[] = useMemo(() => { + const filterFields = getMonitorFilterFields(); + return [...filterFields, 'query']; + }, []); + + const dispatch = useDispatch(); + + const serializeFilterValue = useCallback( + (field: FilterFieldWithQuery, selectedValues: string[] | undefined) => { + if (field === 'query') { + return selectedValues?.length ? selectedValues.toString() : undefined; + } + + return selectedValues && selectedValues.length > 0 + ? JSON.stringify( + selectedValues.map((value) => getSyntheticsFilterKeyForLabel(value, field)) + ) + : undefined; + }, + [] + ); + + const serializeStateValues = useCallback( + (state: FilterStateWithQuery) => { + return filterFieldsWithQuery.reduce( + (acc, cur) => ({ + ...acc, + [cur]: serializeFilterValue( + cur as SyntheticsMonitorFilterField, + state[cur as SyntheticsMonitorFilterField] + ), + }), + {} + ); + }, + [filterFieldsWithQuery, serializeFilterValue] + ); + + const handleFilterChange: SyntheticsMonitorFilterChangeHandler = useCallback( + (field: SyntheticsMonitorFilterField, selectedValues: string[] | undefined) => { + // Update url to reflect the changed filter + updateUrlParams({ + [field]: serializeFilterValue(field, selectedValues), + }); + }, + [serializeFilterValue, updateUrlParams] + ); + + const reduxState = useSelector(selectMonitorFiltersAndQueryState); + const reduxStateSnapshot = JSON.stringify(serializeStateValues(reduxState)); + const urlState = filterFieldsWithQuery.reduce( + (acc, cur) => ({ ...acc, [cur]: urlParams[cur as keyof SyntheticsUrlParams] }), + {} + ); + const urlStateSerializedSnapshot = JSON.stringify(serializeStateValues(urlState)); + + const isUrlHydratedFromRedux = useRef(false); + useEffect(() => { + if (urlStateSerializedSnapshot !== reduxStateSnapshot) { + if ( + urlStateSerializedSnapshot === '{}' && + reduxStateSnapshot !== '{}' && + !isUrlHydratedFromRedux.current + ) { + // Hydrate url only during initialization + updateUrlParams(serializeStateValues(reduxState)); + } else { + dispatch(updateManagementPageStateAction(urlState)); + dispatch(setOverviewPageStateAction(urlState)); + } + } + isUrlHydratedFromRedux.current = true; + + // Only depend on the serialized snapshot + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [urlStateSerializedSnapshot, reduxStateSnapshot]); + + return { handleFilterChange, filterState: reduxState }; +} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.tsx index 6e1a5899c1947..1c93669fb4112 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/common/search_field.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { EuiFieldSearch } from '@elastic/eui'; import useDebounce from 'react-use/lib/useDebounce'; import { i18n } from '@kbn/i18n'; @@ -15,7 +15,7 @@ export function SearchField() { const { query } = useGetUrlParams(); const [_, updateUrlParams] = useUrlParams(); - const [search, setSearch] = useState(query || ''); + const [search, setSearch] = useState(''); useDebounce( () => { @@ -27,13 +27,26 @@ export function SearchField() { [search] ); + // Hydrate search input + const hasInputChangedRef = useRef(false); + useEffect(() => { + if (query && query !== search && !hasInputChangedRef.current) { + setSearch(query); + } + + // Run only to sync url with input + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query]); + return ( { - setSearch(e.target.value); + hasInputChangedRef.current = true; + setSearch(e.target.value ?? ''); }} isClearable={true} aria-label={PLACEHOLDER_TEXT} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.test.tsx index f07805ee99cff..86dabaedd7b28 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.test.tsx @@ -14,8 +14,8 @@ import { WrappedHelper } from '../../../utils/testing'; import { SyntheticsAppState } from '../../../state/root_reducer'; import { selectEncryptedSyntheticsSavedMonitors, - fetchMonitorListAction, - MonitorListPageState, + updateManagementPageStateAction, + MonitorFilterState, } from '../../../state'; import { useMonitorList } from './use_monitor_list'; @@ -23,7 +23,8 @@ import { useMonitorList } from './use_monitor_list'; describe('useMonitorList', () => { let state: SyntheticsAppState; let initialState: Omit, 'loadPage' | 'reloadPage'>; - let defaultPageState: MonitorListPageState; + let filterState: MonitorFilterState; + let filterStateWithQuery: MonitorFilterState & { query?: string | undefined }; const dispatchMockFn = jest.fn(); beforeEach(() => { @@ -40,15 +41,12 @@ describe('useMonitorList', () => { pageState: state.monitorList.pageState, isDataQueried: false, syntheticsMonitors: selectEncryptedSyntheticsSavedMonitors.resultFunc(state.monitorList), + overviewStatus: null, + handleFilterChange: jest.fn(), }; - defaultPageState = { - ...state.monitorList.pageState, - query: '', - locations: [], - monitorType: [], - tags: [], - }; + filterState = { locations: [], monitorTypes: [], projects: [], schedules: [], tags: [] }; + filterStateWithQuery = { ...filterState, query: 'xyz' }; }); afterEach(() => { @@ -60,7 +58,7 @@ describe('useMonitorList', () => { result: { current: hookResult }, } = renderHook(() => useMonitorList(), { wrapper: WrappedHelper }); - expect(hookResult).toMatchObject(initialState); + expect(hookResult).toMatchObject({ ...initialState, handleFilterChange: expect.any(Function) }); }); it('dispatches correct action for query url param', async () => { @@ -79,18 +77,26 @@ describe('useMonitorList', () => { renderHook(() => useMonitorList(), { wrapper: WrapperWithState }); expect(dispatchMockFn).toHaveBeenCalledWith( - fetchMonitorListAction.get({ ...defaultPageState, query }) + updateManagementPageStateAction(filterStateWithQuery) ); }); it('dispatches correct action for filter url param', async () => { - const tags = ['abc', 'xyz']; - const locations = ['loc1', 'loc1']; - const monitorType = ['browser']; + const exp = { + ...filterStateWithQuery, + tags: ['abc', 'xyz'], + locations: ['loc1', 'loc1'], + monitorTypes: ['browser'], + schedules: ['browser'], + projects: ['proj-1'], + query: '', + }; - const url = `/monitor/1?tags=${JSON.stringify(tags)}&locations=${JSON.stringify( - locations - )}&monitorType=${JSON.stringify(monitorType)}`; + const url = `/monitor/1?tags=${JSON.stringify(exp.tags)}&locations=${JSON.stringify( + exp.locations + )}&monitorTypes=${JSON.stringify(exp.monitorTypes)}&schedules=${JSON.stringify( + exp.schedules + )}&projects=${JSON.stringify(exp.projects)}`; jest.useFakeTimers().setSystemTime(Date.now()); const WrapperWithState = ({ children }: { children: React.ReactElement }) => { @@ -103,8 +109,6 @@ describe('useMonitorList', () => { renderHook(() => useMonitorList(), { wrapper: WrapperWithState }); - expect(dispatchMockFn).toHaveBeenCalledWith( - fetchMonitorListAction.get({ ...defaultPageState, tags, locations, monitorType }) - ); + expect(dispatchMockFn).toHaveBeenCalledWith(updateManagementPageStateAction(exp)); }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts index 9b30c5590f950..5963ff30964f9 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/hooks/use_monitor_list.ts @@ -5,16 +5,21 @@ * 2.0. */ -import { useEffect, useCallback, useRef } from 'react'; +import { useCallback, useRef, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; -import { useGetUrlParams } from '../../../hooks'; +import { useDebounce } from 'react-use'; import { fetchMonitorListAction, + quietFetchMonitorListAction, + fetchOverviewStatusAction, MonitorListPageState, selectEncryptedSyntheticsSavedMonitors, selectMonitorListState, + selectOverviewStatus, + updateManagementPageStateAction, } from '../../../state'; +import { useSyntheticsRefreshContext } from '../../../contexts'; +import { useMonitorFiltersState } from '../common/monitor_filters/use_filters'; export function useMonitorList() { const dispatch = useDispatch(); @@ -22,43 +27,44 @@ export function useMonitorList() { const { pageState, loading, loaded, error, data } = useSelector(selectMonitorListState); const syntheticsMonitors = useSelector(selectEncryptedSyntheticsSavedMonitors); + const { status: overviewStatus } = useSelector(selectOverviewStatus); - const { query, tags, monitorType, locations: locationFilters } = useGetUrlParams(); - - const { search } = useLocation(); + const { handleFilterChange } = useMonitorFiltersState(); + const { refreshInterval } = useSyntheticsRefreshContext(); const loadPage = useCallback( - (state: MonitorListPageState) => - dispatch( - fetchMonitorListAction.get({ - ...state, - query, - tags, - monitorType, - locations: locationFilters, - }) - ), - [dispatch, locationFilters, monitorType, query, tags] + (state: MonitorListPageState) => { + dispatch(updateManagementPageStateAction(state)); + }, + [dispatch] ); - const reloadPage = useCallback(() => loadPage(pageState), [pageState, loadPage]); - useEffect(() => { - reloadPage(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [search]); + const reloadPageQuiet = useCallback(() => { + dispatch(quietFetchMonitorListAction(pageState)); + }, [dispatch, pageState]); - // Initial loading + // Periodically refresh useEffect(() => { - if (!loading && !isDataQueriedRef.current) { - isDataQueriedRef.current = true; - reloadPage(); - } + const intervalId = setInterval(() => { + reloadPageQuiet(); + }, refreshInterval); - if (loading) { - isDataQueriedRef.current = true; - } - }, [reloadPage, syntheticsMonitors, loading]); + return () => { + clearInterval(intervalId); + }; + }, [reloadPageQuiet, refreshInterval]); + + useDebounce( + () => { + const overviewStatusArgs = { ...pageState, perPage: pageState.pageSize }; + + dispatch(fetchOverviewStatusAction.get(overviewStatusArgs)); + dispatch(fetchMonitorListAction.get(pageState)); + }, + 500, + [pageState] + ); return { loading, @@ -71,5 +77,7 @@ export function useMonitorList() { reloadPage, isDataQueried: isDataQueriedRef.current, absoluteTotal: data.absoluteTotal ?? 0, + overviewStatus, + handleFilterChange, }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/filter_button.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/filter_button.tsx deleted file mode 100644 index 463c4bbeba003..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/filter_button.tsx +++ /dev/null @@ -1,46 +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, { useState } from 'react'; -import { FieldValueSelection } from '@kbn/observability-plugin/public'; -import { FilterItem } from './filter_group'; -import { useGetUrlParams, useUrlParams } from '../../../../hooks'; - -export const FilterButton = ({ filter }: { filter: FilterItem }) => { - const { label, values, field } = filter; - - const [query, setQuery] = useState(''); - - const [, updateUrlParams] = useUrlParams(); - - const urlParams = useGetUrlParams(); - - return ( - str.toLowerCase().includes(query.toLowerCase())) - : values - } - setQuery={setQuery} - onChange={(selectedValues) => { - updateUrlParams({ - [field]: - selectedValues && selectedValues.length > 0 - ? JSON.stringify(selectedValues) - : undefined, - }); - }} - allowExclusions={false} - loading={false} - asFilterButton={true} - /> - ); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.ts deleted file mode 100644 index 9b9486d5d7398..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/list_filters/use_filters.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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useFetcher } from '@kbn/observability-plugin/public'; -import { useMemo } from 'react'; -import { syntheticsMonitorType } from '../../../../../../../common/types/saved_objects'; - -const aggs = { - types: { - terms: { - field: `${syntheticsMonitorType}.attributes.type.keyword`, - size: 10000, - }, - }, - tags: { - terms: { - field: `${syntheticsMonitorType}.attributes.tags`, - size: 10000, - }, - }, - locations: { - terms: { - field: `${syntheticsMonitorType}.attributes.locations.id`, - size: 10000, - }, - }, -}; - -type Buckets = Array<{ - key: string; - doc_count: number; -}>; - -interface AggsResponse { - types: { - buckets: Buckets; - }; - locations: { - buckets: Buckets; - }; - tags: { - buckets: Buckets; - }; -} - -export const useFilters = () => { - const { savedObjects } = useKibana().services; - - const { data } = useFetcher(async () => { - return savedObjects?.client.find({ - type: syntheticsMonitorType, - perPage: 0, - aggs, - }); - }, []); - - return useMemo(() => { - const { types, tags, locations } = (data?.aggregations as AggsResponse) ?? {}; - return { - types: - types?.buckets?.map(({ key, doc_count: count }) => ({ - label: key, - count, - })) ?? [], - tags: - tags?.buckets?.map(({ key, doc_count: count }) => ({ - label: key, - count, - })) ?? [], - locations: - locations?.buckets?.map(({ key, doc_count: count }) => ({ - label: key, - count, - })) ?? [], - }; - }, [data]); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx index 8b5f57fe7efdc..51b4932bdfe15 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_container.tsx @@ -5,13 +5,12 @@ * 2.0. */ -import React, { useMemo, useEffect } from 'react'; +import React from 'react'; import { EuiSpacer } from '@elastic/eui'; import type { useMonitorList } from '../hooks/use_monitor_list'; import { MonitorAsyncError } from './monitor_errors/monitor_async_error'; -import { useOverviewStatus } from '../hooks/use_overview_status'; -import { ListFilters } from './list_filters/list_filters'; +import { ListFilters } from '../common/monitor_filters/list_filters'; import { MonitorList } from './monitor_list_table/monitor_list'; import { MonitorStats } from './monitor_stats/monitor_stats'; @@ -31,6 +30,8 @@ export const MonitorListContainer = ({ absoluteTotal, loadPage, reloadPage, + overviewStatus, + handleFilterChange, } = monitorListProps; // TODO: Display inline errors in the management table @@ -41,18 +42,6 @@ export const MonitorListContainer = ({ // sortOrder: pageState.sortOrder, // }); - const overviewStatusArgs = useMemo(() => { - return { - pageState: { ...pageState, perPage: pageState.pageSize }, - }; - }, [pageState]); - - const { status, reload: reloadStatus } = useOverviewStatus(overviewStatusArgs); - - useEffect(() => { - reloadStatus(); - }, [reloadStatus, syntheticsMonitors]); - if (!isEnabled && absoluteTotal === 0) { return null; } @@ -60,9 +49,9 @@ export const MonitorListContainer = ({ return ( <> - + - + ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx index 08477bd9eb7d9..57e8d846130cf 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/columns.tsx @@ -34,15 +34,13 @@ import { MonitorLocations } from './monitor_locations'; export function useMonitorListColumns({ canEditSynthetics, - reloadPage, loading, - status, + overviewStatus, setMonitorPendingDeletion, }: { canEditSynthetics: boolean; loading: boolean; - status: OverviewStatusState | null; - reloadPage: () => void; + overviewStatus: OverviewStatusState | null; setMonitorPendingDeletion: (config: EncryptedSyntheticsSavedMonitor) => void; }): Array> { const history = useHistory(); @@ -66,7 +64,7 @@ export function useMonitorListColumns({ ), }, // Only show Project ID column if project monitors are present - ...(status?.projectMonitorsCount ?? 0 > 0 + ...(overviewStatus?.projectMonitorsCount ?? 0 > 0 ? [ { align: 'left' as const, @@ -91,7 +89,7 @@ export function useMonitorListColumns({ ariaLabel={labels.getFilterForTypeMessage(monitor[ConfigKey.MONITOR_TYPE])} onClick={() => { history.push({ - search: `monitorType=${encodeURIComponent( + search: `monitorTypes=${encodeURIComponent( JSON.stringify([monitor[ConfigKey.MONITOR_TYPE]]) )}`, }); @@ -119,7 +117,7 @@ export function useMonitorListColumns({ ) : null, }, @@ -149,7 +147,7 @@ export function useMonitorListColumns({ {}} isSwitchable={!loading} /> ), diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx index b08f64c980a18..2ac8c0f6129d6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_list.tsx @@ -37,7 +37,7 @@ interface Props { loading: boolean; loadPage: (state: MonitorListPageState) => void; reloadPage: () => void; - status: OverviewStatusState | null; + overviewStatus: OverviewStatusState | null; } export const MonitorList = ({ @@ -46,7 +46,7 @@ export const MonitorList = ({ total, error, loading, - status, + overviewStatus, loadPage, reloadPage, }: Props) => { @@ -98,8 +98,7 @@ export const MonitorList = ({ const columns = useMonitorListColumns({ canEditSynthetics, loading, - reloadPage, - status, + overviewStatus, setMonitorPendingDeletion, }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx index 71a52993ad6f7..1a7dc9a7b758b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_stats.tsx @@ -24,7 +24,11 @@ import * as labels from '../labels'; import { MonitorTestRunsCount } from './monitor_test_runs'; import { MonitorTestRunsSparkline } from './monitor_test_runs_sparkline'; -export const MonitorStats = ({ status }: { status: OverviewStatusState | null }) => { +export const MonitorStats = ({ + overviewStatus, +}: { + overviewStatus: OverviewStatusState | null; +}) => { const { euiTheme } = useEuiTheme(); return ( @@ -43,11 +47,11 @@ export const MonitorStats = ({ status }: { status: OverviewStatusState | null }) @@ -64,9 +68,9 @@ export const MonitorStats = ({ status }: { status: OverviewStatusState | null }) - + - + diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx index 8874690f0e0e5..84865837bac6e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx @@ -15,7 +15,7 @@ import { useAbsoluteDate } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; import * as labels from '../labels'; -export const MonitorTestRunsCount = () => { +export const MonitorTestRunsCount = ({ monitorIds }: { monitorIds: string[] }) => { const { observability } = useKibana().services; const theme = useTheme(); @@ -31,11 +31,11 @@ export const MonitorTestRunsCount = () => { { time: { from: absFrom, to: absTo }, reportDefinitions: { - 'monitor.id': [], - 'observer.geo.name': [], + 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], // Show no data when monitorIds is empty }, dataType: 'synthetics', selectedMetricField: 'monitor_total_runs', + filters: [], name: labels.TEST_RUNS_LABEL, color: theme.eui.euiColorVis1, }, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx index 60d565e9417f0..76227cb199bd6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx @@ -14,7 +14,7 @@ import { useAbsoluteDate } from '../../../../hooks'; import { ClientPluginsStart } from '../../../../../../plugin'; import * as labels from '../labels'; -export const MonitorTestRunsSparkline = () => { +export const MonitorTestRunsSparkline = ({ monitorIds }: { monitorIds: string[] }) => { const { observability } = useKibana().services; const { ExploratoryViewEmbeddable } = observability; @@ -34,11 +34,11 @@ export const MonitorTestRunsSparkline = () => { seriesType: 'area', time: { from, to }, reportDefinitions: { - 'monitor.id': [], - 'observer.geo.name': [], + 'monitor.id': monitorIds.length > 0 ? monitorIds : ['false-monitor-id'], // Show no data when monitorIds is empty }, dataType: 'synthetics', selectedMetricField: 'monitor.check_group', + filters: [], name: labels.TEST_RUNS_LABEL, color: theme.eui.euiColorVis1, operationType: 'unique_count', diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx index 6bd9578fa5374..f799643785e80 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/monitors_page.tsx @@ -30,6 +30,13 @@ const MonitorPage: React.FC = () => { useMonitorListBreadcrumbs(); + const { + error: enablementError, + enablement: { isEnabled, canEnable }, + loading: enablementLoading, + enableSynthetics, + } = useEnablement(); + const monitorListProps = useMonitorList(); const { syntheticsMonitors, @@ -38,13 +45,6 @@ const MonitorPage: React.FC = () => { absoluteTotal, } = monitorListProps; - const { - error: enablementError, - enablement: { isEnabled, canEnable }, - loading: enablementLoading, - enableSynthetics, - } = useEnablement(); - const { loading: locationsLoading } = useLocations(); const showEmptyState = isEnabled !== undefined && syntheticsMonitors.length === 0; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx index 1b75d65c891f7..a971b99e5f430 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview_page.tsx @@ -9,13 +9,13 @@ import { EuiFlexGroup, EuiSpacer, EuiFlexItem } from '@elastic/eui'; import { useDispatch, useSelector } from 'react-redux'; import { useTrackPageview } from '@kbn/observability-plugin/public'; import { Redirect, useLocation } from 'react-router-dom'; +import { FilterGroup } from '../common/monitor_filters/filter_group'; import { OverviewAlerts } from './overview/overview_alerts'; -import { useEnablement, useGetUrlParams } from '../../../hooks'; +import { useEnablement } from '../../../hooks'; import { useSyntheticsRefreshContext } from '../../../contexts/synthetics_refresh_context'; import { fetchMonitorOverviewAction, quietFetchOverviewAction, - setOverviewPageStateAction, selectOverviewPageState, selectServiceLocationsState, } from '../../../state'; @@ -40,7 +40,6 @@ export const OverviewPage: React.FC = () => { const dispatch = useDispatch(); const { lastRefresh } = useSyntheticsRefreshContext(); - const { query } = useGetUrlParams(); const { search } = useLocation(); const pageState = useSelector(selectOverviewPageState); @@ -52,14 +51,6 @@ export const OverviewPage: React.FC = () => { } }, [dispatch, locationsLoaded, locationsLoading]); - // fetch overview for query state changes - useEffect(() => { - if (pageState.query !== query) { - dispatch(fetchMonitorOverviewAction.get({ ...pageState, query })); - dispatch(setOverviewPageStateAction({ query })); - } - }, [dispatch, pageState, query]); - // fetch overview for all other page state changes useEffect(() => { dispatch(fetchMonitorOverviewAction.get(pageState)); @@ -75,13 +66,19 @@ export const OverviewPage: React.FC = () => { loading: enablementLoading, } = useEnablement(); - const { syntheticsMonitors, loading: monitorsLoading, loaded: monitorsLoaded } = useMonitorList(); + const { + syntheticsMonitors, + loading: monitorsLoading, + loaded: monitorsLoaded, + handleFilterChange, + } = useMonitorList(); if ( !search && !enablementLoading && isEnabled && !monitorsLoading && + monitorsLoaded && syntheticsMonitors.length === 0 ) { return ; @@ -99,13 +96,16 @@ export const OverviewPage: React.FC = () => { return ( <> - + + + + {Boolean(!monitorsLoaded || syntheticsMonitors?.length > 0) && ( diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts index 4ca92c3390f1d..11f8a6ddbd43b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/common/network_data/data_formatting.ts @@ -495,6 +495,10 @@ export const formatMillisecond = ( ms: number, { maxMillis = 1000, digits }: { digits?: number; maxMillis?: number } ) => { + if (ms < 0) { + return '--'; + } + if (ms < maxMillis) { return `${ms.toFixed(digits ?? 0)} ms`; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts index 0962bf7aeebf3..6f53af4cb75be 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings.ts @@ -18,7 +18,6 @@ import { SYNTHETICS_TOTAL_TIMINGS, SYNTHETICS_WAIT_TIMINGS, } from '@kbn/observability-plugin/common'; -import { CONTENT_SIZE_LABEL } from './use_network_timings_prev'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; @@ -58,12 +57,8 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe index: SYNTHETICS_INDEX_PATTERN, body: { size: 0, - runtime_mappings: { - ...runTimeMappings, - 'synthetics.payload.transfer_size': { - type: 'long', - }, - }, + runtime_mappings: runTimeMappings, + query: { bool: { filter: [ @@ -82,7 +77,7 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe field: SYNTHETICS_DNS_TIMINGS, }, }, - ssl: { + tls: { sum: { field: SYNTHETICS_SSL_TIMINGS, }, @@ -117,11 +112,6 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe field: SYNTHETICS_TOTAL_TIMINGS, }, }, - transferSize: { - sum: { - field: 'synthetics.payload.transfer_size', - }, - }, }, }, }, @@ -138,68 +128,67 @@ export const useNetworkTimings = (checkGroupIdArg?: string, stepIndexArg?: numbe send: aggs?.send.value ?? 0, wait: aggs?.wait.value ?? 0, blocked: aggs?.blocked.value ?? 0, - ssl: aggs?.ssl.value ?? 0, - transferSize: aggs?.transferSize.value ?? 0, + tls: aggs?.tls.value ?? 0, }; return { timings, - transferSize: { - value: timings.transferSize, - label: CONTENT_SIZE_LABEL, - }, - timingsWithLabels: [ - { - value: timings.dns, - label: SYNTHETICS_DNS_TIMINGS_LABEL, - }, - { - value: timings.ssl, - label: SYNTHETICS_SSL_TIMINGS_LABEL, - }, - { - value: timings.blocked, - label: SYNTHETICS_BLOCKED_TIMINGS_LABEL, - }, - { - value: timings.connect, - label: SYNTHETICS_CONNECT_TIMINGS_LABEL, - }, - { - value: timings.receive, - label: SYNTHETICS_RECEIVE_TIMINGS_LABEL, - }, - { - value: timings.send, - label: SYNTHETICS_SEND_TIMINGS_LABEL, - }, - { - value: timings.wait, - label: SYNTHETICS_WAIT_TIMINGS_LABEL, - }, - ].sort((a, b) => b.value - a.value), + timingsWithLabels: getTimingWithLabels(timings), }; }; -const SYNTHETICS_CONNECT_TIMINGS_LABEL = i18n.translate('xpack.synthetics.connect.label', { +export const getTimingWithLabels = (timings: Record) => { + return [ + { + value: timings.blocked, + label: SYNTHETICS_BLOCKED_TIMINGS_LABEL, + }, + { + value: timings.dns, + label: SYNTHETICS_DNS_TIMINGS_LABEL, + }, + { + value: timings.connect, + label: SYNTHETICS_CONNECT_TIMINGS_LABEL, + }, + { + value: timings.tls, + label: SYNTHETICS_TLS_TIMINGS_LABEL, + }, + { + value: timings.wait, + label: SYNTHETICS_WAIT_TIMINGS_LABEL, + }, + { + value: timings.receive, + label: SYNTHETICS_RECEIVE_TIMINGS_LABEL, + }, + { + value: timings.send, + label: SYNTHETICS_SEND_TIMINGS_LABEL, + }, + ]; +}; + +export const SYNTHETICS_CONNECT_TIMINGS_LABEL = i18n.translate('xpack.synthetics.connect.label', { defaultMessage: 'Connect', }); -const SYNTHETICS_DNS_TIMINGS_LABEL = i18n.translate('xpack.synthetics.dns', { +export const SYNTHETICS_DNS_TIMINGS_LABEL = i18n.translate('xpack.synthetics.dns', { defaultMessage: 'DNS', }); -const SYNTHETICS_WAIT_TIMINGS_LABEL = i18n.translate('xpack.synthetics.wait', { +export const SYNTHETICS_WAIT_TIMINGS_LABEL = i18n.translate('xpack.synthetics.wait', { defaultMessage: 'Wait', }); -const SYNTHETICS_SSL_TIMINGS_LABEL = i18n.translate('xpack.synthetics.ssl', { - defaultMessage: 'SSL', +export const SYNTHETICS_TLS_TIMINGS_LABEL = i18n.translate('xpack.synthetics.tls', { + defaultMessage: 'TLS', }); -const SYNTHETICS_BLOCKED_TIMINGS_LABEL = i18n.translate('xpack.synthetics.blocked', { +export const SYNTHETICS_BLOCKED_TIMINGS_LABEL = i18n.translate('xpack.synthetics.blocked', { defaultMessage: 'Blocked', }); -const SYNTHETICS_SEND_TIMINGS_LABEL = i18n.translate('xpack.synthetics.send', { +export const SYNTHETICS_SEND_TIMINGS_LABEL = i18n.translate('xpack.synthetics.send', { defaultMessage: 'Send', }); -const SYNTHETICS_RECEIVE_TIMINGS_LABEL = i18n.translate('xpack.synthetics.receive', { +export const SYNTHETICS_RECEIVE_TIMINGS_LABEL = i18n.translate('xpack.synthetics.receive', { defaultMessage: 'Receive', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts index d59a551e58767..ec6f96e15259c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_network_timings_prev.ts @@ -22,6 +22,7 @@ import moment from 'moment'; import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; import { useReduxEsSearch } from '../../../hooks/use_redux_es_search'; +import { getTimingWithLabels } from './use_network_timings'; export const useStepFilters = (checkGroupId: string, stepIndex: number) => { return [ @@ -38,11 +39,15 @@ export const useStepFilters = (checkGroupId: string, stepIndex: number) => { ]; }; -export const useNetworkTimingsPrevious24Hours = (stepIndexArg?: number, timestampArg?: string) => { +export const useNetworkTimingsPrevious24Hours = ( + stepIndexArg?: number, + timestampArg?: string, + checkGroupIdArg?: string +) => { const params = useParams<{ checkGroupId: string; stepIndex: string; monitorId: string }>(); const configId = params.monitorId; - const checkGroupId = params.checkGroupId; + const checkGroupId = checkGroupIdArg ?? params.checkGroupId; const stepIndex = stepIndexArg ?? Number(params.stepIndex); const { currentStep } = useJourneySteps(); @@ -64,12 +69,7 @@ export const useNetworkTimingsPrevious24Hours = (stepIndexArg?: number, timestam index: SYNTHETICS_INDEX_PATTERN, body: { size: 0, - runtime_mappings: { - ...runTimeMappings, - 'synthetics.payload.transfer_size': { - type: 'long', - }, - }, + runtime_mappings: runTimeMappings, query: { bool: { filter: [ @@ -153,11 +153,6 @@ export const useNetworkTimingsPrevious24Hours = (stepIndexArg?: number, timestam field: SYNTHETICS_TOTAL_TIMINGS, }, }, - transferSize: { - sum: { - field: 'synthetics.payload.transfer_size', - }, - }, }, }, }, @@ -179,7 +174,6 @@ export const useNetworkTimingsPrevious24Hours = (stepIndexArg?: number, timestam const wait: number[] = []; const blocked: number[] = []; const ssl: number[] = []; - const transferSize: number[] = []; aggs?.testRuns.buckets.forEach((bucket) => { dns.push(bucket.dns.value ?? 0); @@ -189,7 +183,6 @@ export const useNetworkTimingsPrevious24Hours = (stepIndexArg?: number, timestam wait.push(bucket.wait.value ?? 0); blocked.push(bucket.blocked.value ?? 0); ssl.push(bucket.ssl.value ?? 0); - transferSize.push(bucket.transferSize.value ?? 0); }); const timings = { @@ -200,79 +193,22 @@ export const useNetworkTimingsPrevious24Hours = (stepIndexArg?: number, timestam wait: median(wait), blocked: median(blocked), ssl: median(ssl), - transferSize: median(transferSize), }; return { loading, timings, - transferSizePrev: { - value: timings.transferSize, - label: CONTENT_SIZE_LABEL, - }, - timingsWithLabels: [ - { - value: timings.dns, - label: SYNTHETICS_DNS_TIMINGS_LABEL, - }, - { - value: timings.ssl, - label: SYNTHETICS_SSL_TIMINGS_LABEL, - }, - { - value: timings.blocked, - label: SYNTHETICS_BLOCKED_TIMINGS_LABEL, - }, - { - value: timings.connect, - label: SYNTHETICS_CONNECT_TIMINGS_LABEL, - }, - { - value: timings.receive, - label: SYNTHETICS_RECEIVE_TIMINGS_LABEL, - }, - { - value: timings.send, - label: SYNTHETICS_SEND_TIMINGS_LABEL, - }, - { - value: timings.wait, - label: SYNTHETICS_WAIT_TIMINGS_LABEL, - }, - ].sort((a, b) => b.value - a.value), + timingsWithLabels: getTimingWithLabels(timings), }; }; -const median = (arr: number[]): number => { +export const median = (arr: number[]): number => { if (!arr.length) return 0; const s = [...arr].sort((a, b) => a - b); const mid = Math.floor(s.length / 2); return s.length % 2 === 0 ? (s[mid - 1] + s[mid]) / 2 : s[mid]; }; -const SYNTHETICS_CONNECT_TIMINGS_LABEL = i18n.translate('xpack.synthetics.connect.label', { - defaultMessage: 'Connect', -}); -const SYNTHETICS_DNS_TIMINGS_LABEL = i18n.translate('xpack.synthetics.dns', { - defaultMessage: 'DNS', -}); -const SYNTHETICS_WAIT_TIMINGS_LABEL = i18n.translate('xpack.synthetics.wait', { - defaultMessage: 'Wait', -}); - -const SYNTHETICS_SSL_TIMINGS_LABEL = i18n.translate('xpack.synthetics.ssl', { - defaultMessage: 'SSL', -}); -const SYNTHETICS_BLOCKED_TIMINGS_LABEL = i18n.translate('xpack.synthetics.blocked', { - defaultMessage: 'Blocked', -}); -const SYNTHETICS_SEND_TIMINGS_LABEL = i18n.translate('xpack.synthetics.send', { - defaultMessage: 'Send', -}); -const SYNTHETICS_RECEIVE_TIMINGS_LABEL = i18n.translate('xpack.synthetics.receive', { - defaultMessage: 'Receive', -}); - export const CONTENT_SIZE_LABEL = i18n.translate('xpack.synthetics.contentSize', { defaultMessage: 'Content Size', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_filters.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_filters.ts deleted file mode 100644 index 6828cdf69633d..0000000000000 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_filters.ts +++ /dev/null @@ -1,24 +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 { useParams } from 'react-router-dom'; - -export const useStepFilters = (prevCheckGroupId?: string) => { - const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); - return [ - { - term: { - 'monitor.check_group': prevCheckGroupId ?? checkGroupId, - }, - }, - { - term: { - 'synthetics.step.index': Number(stepIndex), - }, - }, - ]; -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts index 3db64c5113305..7c7d1ecb89828 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_metrics.ts @@ -6,8 +6,19 @@ */ import { useEsSearch } from '@kbn/observability-plugin/public'; -import { useStepFilters } from './use_step_filters'; +import { useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { formatBytes } from './use_object_metrics'; +import { formatMillisecond } from '../step_metrics/step_metrics'; +import { + CLS_HELP_LABEL, + DCL_TOOLTIP, + FCP_TOOLTIP, + LCP_HELP_LABEL, + TRANSFER_SIZE_HELP, +} from '../step_metrics/labels'; import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; +import { JourneyStep } from '../../../../../../common/runtime_types'; export const MONITOR_DURATION_US = 'monitor.duration.us'; export const SYNTHETICS_CLS = 'browser.experience.cls'; @@ -20,12 +31,14 @@ export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us'; export type StepMetrics = ReturnType; -export const useStepMetrics = (loadData = true, prevCheckGroupId?: string) => { - const esIndex = loadData ? SYNTHETICS_INDEX_PATTERN : ''; +export const useStepMetrics = (step?: JourneyStep) => { + const urlParams = useParams<{ checkGroupId: string; stepIndex: string }>(); + const checkGroupId = step?.monitor.check_group ?? urlParams.checkGroupId; + const stepIndex = step?.synthetics.step?.index ?? urlParams.stepIndex; const { data } = useEsSearch( { - index: esIndex, + index: SYNTHETICS_INDEX_PATTERN, body: { size: 0, query: { @@ -36,7 +49,16 @@ export const useStepMetrics = (loadData = true, prevCheckGroupId?: string) => { 'synthetics.type': ['step/metrics', 'step/end'], }, }, - ...useStepFilters(prevCheckGroupId), + { + term: { + 'monitor.check_group': checkGroupId, + }, + }, + { + term: { + 'synthetics.step.index': Number(stepIndex), + }, + }, ], }, }, @@ -69,13 +91,13 @@ export const useStepMetrics = (loadData = true, prevCheckGroupId?: string) => { }, }, }, - [esIndex], - { name: prevCheckGroupId ? 'previousStepMetrics' : 'stepMetrics' } + [stepIndex, checkGroupId], + { name: 'stepMetrics' } ); const { data: transferData } = useEsSearch( { - index: esIndex, + index: SYNTHETICS_INDEX_PATTERN, body: { size: 0, runtime_mappings: { @@ -94,7 +116,16 @@ export const useStepMetrics = (loadData = true, prevCheckGroupId?: string) => { 'synthetics.type': 'journey/network_info', }, }, - ...useStepFilters(prevCheckGroupId), + { + term: { + 'monitor.check_group': checkGroupId, + }, + }, + { + term: { + 'synthetics.step.index': Number(stepIndex), + }, + }, ], }, }, @@ -112,17 +143,80 @@ export const useStepMetrics = (loadData = true, prevCheckGroupId?: string) => { }, }, }, - [esIndex], + [stepIndex, checkGroupId], { - name: prevCheckGroupId - ? 'previousStepMetricsFromNetworkInfos' - : 'stepMetricsFromNetworkInfos', + name: 'stepMetricsFromNetworkInfos', } ); + const metrics = data?.aggregations; + const transferDataVal = transferData?.aggregations?.transferSize?.value ?? 0; + return { ...(data?.aggregations ?? {}), - transferData: ((transferData?.aggregations?.transferSize?.value ?? 0) / 1e6).toFixed(0), - resourceSize: ((transferData?.aggregations?.resourceSize?.value ?? 0) / 1e6).toFixed(0), + transferData: transferData?.aggregations?.transferSize?.value ?? 0, + resourceSize: transferData?.aggregations?.resourceSize?.value ?? 0, + + metrics: [ + { + label: STEP_DURATION_LABEL, + value: metrics?.totalDuration.value, + formatted: formatMillisecond((metrics?.totalDuration.value ?? 0) / 1000), + }, + { + value: metrics?.lcp.value, + label: LCP_LABEL, + helpText: LCP_HELP_LABEL, + formatted: formatMillisecond((metrics?.lcp.value ?? 0) / 1000), + }, + { + value: metrics?.fcp.value, + label: FCP_LABEL, + helpText: FCP_TOOLTIP, + formatted: formatMillisecond((metrics?.fcp.value ?? 0) / 1000), + }, + { + value: metrics?.cls.value, + label: CLS_LABEL, + helpText: CLS_HELP_LABEL, + formatted: metrics?.cls.value ?? 0, + }, + { + value: metrics?.dcl.value, + label: DCL_LABEL, + helpText: DCL_TOOLTIP, + formatted: formatMillisecond((metrics?.dcl.value ?? 0) / 1000), + }, + { + value: transferDataVal, + label: TRANSFER_SIZE, + helpText: TRANSFER_SIZE_HELP, + formatted: formatBytes(transferDataVal ?? 0), + }, + ], }; }; + +export const LCP_LABEL = i18n.translate('xpack.synthetics.lcp.label', { + defaultMessage: 'LCP', +}); + +export const FCP_LABEL = i18n.translate('xpack.synthetics.fcp.label', { + defaultMessage: 'FCP', +}); + +export const CLS_LABEL = i18n.translate('xpack.synthetics.cls.label', { + defaultMessage: 'CLS', +}); + +export const DCL_LABEL = i18n.translate('xpack.synthetics.dcl.label', { + defaultMessage: 'DCL', +}); + +export const STEP_DURATION_LABEL = i18n.translate('xpack.synthetics.totalDuration.metrics', { + defaultMessage: 'Step duration', +}); + +export const TRANSFER_SIZE = i18n.translate('xpack.synthetics.totalDuration.transferSize', { + defaultMessage: 'Transfer size', +}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts index bf8f1e14259de..d29c142c03083 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/hooks/use_step_prev_metrics.ts @@ -6,8 +6,20 @@ */ import { useParams } from 'react-router-dom'; -import { useJourneySteps } from '../../monitor_details/hooks/use_journey_steps'; -import { StepMetrics, useStepMetrics } from './use_step_metrics'; +import { useEsSearch } from '@kbn/observability-plugin/public'; +import { formatBytes } from './use_object_metrics'; +import { formatMillisecond } from '../step_metrics/step_metrics'; +import { + CLS_LABEL, + DCL_LABEL, + FCP_LABEL, + LCP_LABEL, + STEP_DURATION_LABEL, + TRANSFER_SIZE, +} from './use_step_metrics'; +import { JourneyStep } from '../../../../../../common/runtime_types'; +import { median } from './use_network_timings_prev'; +import { SYNTHETICS_INDEX_PATTERN } from '../../../../../../common/constants'; export const MONITOR_DURATION_US = 'monitor.duration.us'; export const SYNTHETICS_CLS = 'browser.experience.cls'; @@ -18,33 +30,218 @@ export const SYNTHETICS_DCL = 'browser.experience.dcl.us'; export const SYNTHETICS_STEP_NAME = 'synthetics.step.name.keyword'; export const SYNTHETICS_STEP_DURATION = 'synthetics.step.duration.us'; -export const useStepPrevMetrics = (stepMetrics: StepMetrics) => { - const { checkGroupId } = useParams<{ checkGroupId: string; stepIndex: string }>(); +export const useStepPrevMetrics = (step?: JourneyStep) => { + const urlParams = useParams<{ + checkGroupId: string; + stepIndex: string; + monitorId: string; + }>(); - const { data } = useJourneySteps(checkGroupId); + const monitorId = urlParams.monitorId; + const checkGroupId = step?.monitor.check_group ?? urlParams.checkGroupId; + const stepIndex = step?.synthetics.step?.index ?? urlParams.stepIndex; - const prevCheckGroupId = data?.details?.previous?.checkGroup; + const { data, loading } = useEsSearch( + { + index: SYNTHETICS_INDEX_PATTERN, + body: { + size: 0, + query: { + bool: { + filter: [ + { + terms: { + 'synthetics.type': ['step/metrics', 'step/end'], + }, + }, + { + term: { + 'synthetics.step.index': Number(stepIndex), + }, + }, + { + term: { + config_id: monitorId, + }, + }, + { + range: { + '@timestamp': { + lte: 'now', + gte: 'now-24h/h', + }, + }, + }, + ], + }, + }, + aggs: { + testRuns: { + terms: { + field: 'monitor.check_group', + size: 10000, + }, + aggs: { + fcp: { + sum: { + field: SYNTHETICS_FCP, + }, + }, + lcp: { + sum: { + field: SYNTHETICS_LCP, + }, + }, + cls: { + sum: { + field: SYNTHETICS_CLS, + }, + }, + dcl: { + sum: { + field: SYNTHETICS_DCL, + }, + }, + stepDuration: { + sum: { + field: SYNTHETICS_STEP_DURATION, + }, + }, + }, + }, + }, + }, + }, + [monitorId, checkGroupId, stepIndex], + { name: 'previousStepMetrics' } + ); + const { data: transferData } = useEsSearch( + { + index: SYNTHETICS_INDEX_PATTERN, + body: { + size: 0, + runtime_mappings: { + 'synthetics.payload.transfer_size': { + type: 'double', + }, + 'synthetics.payload.resource_size': { + type: 'double', + }, + }, + query: { + bool: { + filter: [ + { + term: { + 'synthetics.type': 'journey/network_info', + }, + }, + { + term: { + 'synthetics.step.index': Number(stepIndex), + }, + }, + { + term: { + config_id: monitorId, + }, + }, + { + range: { + '@timestamp': { + lte: 'now', + gte: 'now-24h/h', + }, + }, + }, + ], + }, + }, + aggs: { + testRuns: { + terms: { + field: 'monitor.check_group', + size: 10000, + }, + aggs: { + transferSize: { + sum: { + field: 'synthetics.payload.transfer_size', + }, + }, + }, + }, + }, + }, + }, + [monitorId, checkGroupId, stepIndex], + { + name: 'previousStepMetricsFromNetworkInfos', + } + ); - const prevMetrics = useStepMetrics(Boolean(prevCheckGroupId), prevCheckGroupId); + const metrics = data?.aggregations; - const fcpThreshold = findThreshold(stepMetrics?.fcp?.value, prevMetrics?.fcp?.value); - const lcpThreshold = findThreshold(stepMetrics?.lcp?.value, prevMetrics?.lcp?.value); - const clsThreshold = findThreshold(stepMetrics?.cls?.value, prevMetrics?.cls?.value); - const dclThreshold = findThreshold(stepMetrics?.dcl?.value, prevMetrics?.dcl?.value); - const totalThreshold = findThreshold( - stepMetrics?.totalDuration?.value, - prevMetrics?.totalDuration?.value - ); + const transferSize: number[] = []; + transferData?.aggregations?.testRuns.buckets.forEach((bucket) => { + transferSize.push(bucket.transferSize.value ?? 0); + }); + + const medianTransferSize = median(transferSize); + + const lcp: number[] = []; + const fcp: number[] = []; + const cls: number[] = []; + const dcl: number[] = []; + const stepDuration: number[] = []; + + metrics?.testRuns.buckets.forEach((bucket) => { + lcp.push(bucket.lcp.value ?? 0); + fcp.push(bucket.fcp.value ?? 0); + cls.push(bucket.cls.value ?? 0); + dcl.push(bucket.dcl.value ?? 0); + stepDuration.push(bucket.stepDuration.value ?? 0); + }); + + const medianLcp = median(lcp); + const medianFcp = median(fcp); + const medianCls = median(cls); + const medianDcl = median(dcl); + const medianStepDuration = median(stepDuration); return { - fcpThreshold, - lcpThreshold, - clsThreshold, - dclThreshold, - totalThreshold, + loading, + metrics: [ + { + label: STEP_DURATION_LABEL, + value: medianStepDuration, + formatted: formatMillisecond(medianStepDuration / 1000), + }, + { + value: medianLcp, + label: LCP_LABEL, + formatted: formatMillisecond(medianLcp / 1000), + }, + { + value: medianFcp, + label: FCP_LABEL, + formatted: formatMillisecond(medianFcp / 1000), + }, + { + value: medianCls, + label: CLS_LABEL, + formatted: medianCls, + }, + { + value: medianDcl, + label: DCL_LABEL, + formatted: formatMillisecond(medianDcl / 1000), + }, + { + value: medianTransferSize, + label: TRANSFER_SIZE, + formatted: formatBytes(medianTransferSize), + }, + ], }; }; - -const findThreshold = (current?: number | null, prev?: number | null) => { - return -1 * (100 - ((current ?? 0) / (prev ?? 0)) * 100); -}; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/labels.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/labels.ts index f8ab854773634..ee2820306e754 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/labels.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/labels.ts @@ -27,6 +27,11 @@ export const DCL_TOOLTIP = i18n.translate('xpack.synthetics.coreVitals.dclToolti 'Triggered when the browser completes parsing the document. Helpful when there are multiple listeners, or logic is executed: domContentLoadedEventEnd - domContentLoadedEventStart.', }); +export const TRANSFER_SIZE_HELP = i18n.translate('xpack.synthetics.fieldLabels.transferSize', { + defaultMessage: + 'The transferSize property represents the size of the fetched resource. The size includes the response header fields plus the response payload body', +}); + export const LCP_LABEL = i18n.translate('xpack.synthetics.fieldLabels.lcp', { defaultMessage: 'Largest contentful paint (LCP)', }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx index 1539c98111c5f..fdcb874e0b48d 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_metrics/step_metrics.tsx @@ -6,23 +6,18 @@ */ import React from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiStat, - EuiTitle, - EuiIcon, - EuiIconTip, - EuiFlexGrid, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiFlexGrid } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { CLS_HELP_LABEL, DCL_TOOLTIP, FCP_TOOLTIP, LCP_HELP_LABEL } from './labels'; +import { ThresholdIndicator } from '../../common/components/thershold_indicator'; import { DefinitionsPopover } from './definitions_popover'; import { useStepMetrics } from '../hooks/use_step_metrics'; import { useStepPrevMetrics } from '../hooks/use_step_prev_metrics'; export const formatMillisecond = (ms: number) => { + if (ms < 0) { + return '- ms'; + } + if (ms < 1000) { return `${ms.toFixed(0)} ms`; } @@ -30,10 +25,8 @@ export const formatMillisecond = (ms: number) => { }; export const StepMetrics = () => { - const stepMetrics = useStepMetrics(); - - const { fcpThreshold, lcpThreshold, clsThreshold, dclThreshold, totalThreshold } = - useStepPrevMetrics(stepMetrics); + const { metrics: stepMetrics } = useStepMetrics(); + const { metrics: prevMetrics, loading } = useStepPrevMetrics(); return ( <> @@ -50,108 +43,30 @@ export const StepMetrics = () => { - - - - - - - - - - - - - - - - - - - + {stepMetrics.map(({ label, value, helpText, formatted }) => { + const prevVal = prevMetrics.find((prev) => prev.label === label); + + if (label) + return ( + + + + ); + })} ); }; -const StatThreshold = ({ - title, - threshold, - description, - helpText, -}: { - threshold: number; - title: number | string; - description: string; - helpText?: string; -}) => { - const isUp = threshold >= 5; - const isDown = threshold < 5; - - const isSame = (!isUp && !isDown) || !isFinite(threshold); - return ( - - - - {description} {helpText && } - - } - title={ - <> - {title} - - {isSame ? ( - - ) : ( - - )} - - - } - reverse={true} - /> - - - ); -}; - const METRICS_LABEL = i18n.translate('xpack.synthetics.stepDetailsRoute.metrics', { defaultMessage: 'Metrics', }); - -const TOTAL_DURATION_LABEL = i18n.translate('xpack.synthetics.totalDuration.metrics', { - defaultMessage: 'Step duration', -}); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx index 14cf878b1550e..12cd39602acdd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_now_mode/test_result_header.tsx @@ -16,6 +16,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import * as React from 'react'; +import { getTestRunDetailLink } from '../common/links/test_details_link'; +import { useLocations } from '../../hooks'; import { useSyntheticsSettingsContext } from '../../contexts'; import { JourneyStep, Ping } from '../../../../../common/runtime_types'; import { formatDuration } from '../../utils/formatting'; @@ -45,6 +47,8 @@ export function TestResultHeader({ }); } + const { getLocationByLabel } = useLocations(); + const summaryDoc = summaryDocs?.[0] as Ping; return ( @@ -84,11 +88,15 @@ export function TestResultHeader({ )} - {checkGroupId && ( + {checkGroupId && configId && isCompleted && ( {VIEW_DETAILS} diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_locations.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_locations.ts index 8469ff691d7c8..5137eb3b583b6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_locations.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_locations.ts @@ -21,10 +21,15 @@ export function useLocations() { } }, [dispatch, locations, locationsLoaded]); + const getLocationByLabel = (label: string) => { + return locations.find((location) => location.label === label); + }; + return { error, loading, locations, throttling, + getLocationByLabel, }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx index d2452a40f2426..d530b557fa356 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx @@ -7,15 +7,16 @@ import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types'; import React, { FC, useEffect } from 'react'; -import { EuiLink, useEuiTheme } from '@elastic/eui'; +import { EuiButtonEmpty, EuiLink, useEuiTheme } from '@elastic/eui'; import { Route, Switch, useHistory } from 'react-router-dom'; import { OutPortal } from 'react-reverse-portal'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { NotFoundPrompt } from '@kbn/shared-ux-prompt-not-found'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useInspectorContext } from '@kbn/observability-plugin/public'; import type { LazyObservabilityPageTemplateProps } from '@kbn/observability-plugin/public'; +import { useInspectorContext } from '@kbn/observability-plugin/public'; import { ClientPluginsStart } from '../../plugin'; import { getMonitorsRoute } from './components/monitors_page/route_config'; import { getMonitorDetailsRoute } from './components/monitor_details/route_config'; @@ -26,10 +27,9 @@ import { TestRunDetails } from './components/test_run_details/test_run_details'; import { MonitorAddPageWithServiceAllowed } from './components/monitor_add_edit/monitor_add_page'; import { MonitorEditPageWithServiceAllowed } from './components/monitor_add_edit/monitor_edit_page'; import { GettingStartedPage } from './components/getting_started/getting_started_page'; -import { NotFoundPage } from './components/common/pages/not_found'; import { - MonitorTypePortalNode, MonitorDetailsLinkPortalNode, + MonitorTypePortalNode, } from './components/monitor_add_edit/portals'; import { GETTING_STARTED_ROUTE, @@ -210,7 +210,27 @@ export const PageRouter: FC = () => { ) )} - + ( + + { + application.navigateToApp(PLUGIN.SYNTHETICS_PLUGIN_ID); + }} + > + {i18n.translate('xpack.synthetics.routes.goToSynthetics', { + defaultMessage: 'Go to Synthetics Home Page', + })} + , + ]} + /> + + )} + /> ); }; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index fb400f97fd729..b43fb185ea652 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -5,16 +5,10 @@ * 2.0. */ -import { ErrorToastOptions } from '@kbn/core-notifications-browser'; import { createAction } from '@reduxjs/toolkit'; -import { UpsertMonitorResponse } from '..'; -import { - EncryptedSyntheticsMonitor, - MonitorManagementListResult, - SyntheticsMonitor, -} from '../../../../../common/runtime_types'; +import { UpsertMonitorError, UpsertMonitorRequest, UpsertMonitorResponse } from '..'; +import { MonitorManagementListResult } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; -import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; @@ -22,29 +16,9 @@ export const fetchMonitorListAction = createAsyncAction< MonitorListPageState, MonitorManagementListResult >('fetchMonitorListAction'); - -interface ToastParams { - message: MessageType; - lifetimeMs: number; - testAttribute?: string; -} - -export interface UpsertMonitorRequest { - configId: string; - monitor: Partial | Partial; - success: ToastParams; - error: ToastParams; - /** - * The effect will perform a quiet refresh of the overview state - * after a successful upsert. The default behavior is to perform the fetch. - */ - shouldQuietFetchAfterSuccess?: boolean; -} - -interface UpsertMonitorError { - configId: string; - error: IHttpSerializedFetchError; -} +export const quietFetchMonitorListAction = createAction( + 'quietFetchMonitorListAction' +); export const fetchUpsertMonitorAction = createAction('fetchUpsertMonitor'); export const fetchUpsertSuccessAction = createAction<{ @@ -62,3 +36,7 @@ export const enableMonitorAlertAction = createAsyncAction< >('enableMonitorAlertAction'); export const clearMonitorUpsertStatus = createAction('clearMonitorUpsertStatus'); + +export const updateManagementPageStateAction = createAction>( + 'updateManagementPageState' +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts index 0956fad85cfd6..9d1f7c21f5696 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/api.ts @@ -31,7 +31,9 @@ function toMonitorManagementListQueryArgs( query: pageState.query, tags: pageState.tags, locations: pageState.locations, - monitorType: pageState.monitorType, + monitorTypes: pageState.monitorTypes, + projects: pageState.projects, + schedules: pageState.schedules, searchFields: [], }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts index 4520a9d0c6f0c..14f0be3493ac6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts @@ -6,9 +6,10 @@ */ import { PayloadAction } from '@reduxjs/toolkit'; -import { call, put, takeEvery, takeLeading, select } from 'redux-saga/effects'; +import { call, put, takeEvery, select, debounce } from 'redux-saga/effects'; import { SavedObject } from '@kbn/core-saved-objects-common'; import { enableDefaultAlertingAction } from '../alert_rules'; +import { ConfigKey, SyntheticsMonitor } from '../../../../../common/runtime_types'; import { kibanaService } from '../../../../utils/kibana_service'; import { MonitorOverviewPageState, quietFetchOverviewStatusAction } from '../overview'; import { quietFetchOverviewAction } from '../overview/actions'; @@ -22,15 +23,16 @@ import { fetchUpsertFailureAction, fetchUpsertMonitorAction, fetchUpsertSuccessAction, - UpsertMonitorRequest, + quietFetchMonitorListAction, } from './actions'; import { fetchMonitorManagementList, fetchUpsertMonitor } from './api'; import { toastTitle } from './toast_title'; -import { ConfigKey, SyntheticsMonitor } from '../../../../../common/runtime_types'; +import { UpsertMonitorRequest } from './models'; export function* fetchMonitorListEffect() { - yield takeLeading( - fetchMonitorListAction.get, + yield debounce( + 300, // Only take the latest while ignoring any intermediate triggers + [fetchMonitorListAction.get, quietFetchMonitorListAction], fetchEffectFactory( fetchMonitorManagementList, fetchMonitorListAction.success, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index ff07001f74ef3..e5086dbb63b21 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { isEqual } from 'lodash'; import { createReducer } from '@reduxjs/toolkit'; import { FETCH_STATUS } from '@kbn/observability-plugin/public'; @@ -26,6 +25,7 @@ import { fetchUpsertFailureAction, fetchUpsertMonitorAction, fetchUpsertSuccessAction, + updateManagementPageStateAction, } from './actions'; export interface MonitorListState { @@ -56,10 +56,10 @@ const initialState: MonitorListState = { export const monitorListReducer = createReducer(initialState, (builder) => { builder - .addCase(fetchMonitorListAction.get, (state, action) => { - if (!isEqual(state.pageState, action.payload)) { - state.pageState = action.payload; - } + .addCase(updateManagementPageStateAction, (state, action) => { + state.pageState = { ...state.pageState, ...action.payload }; + }) + .addCase(fetchMonitorListAction.get, (state) => { state.loading = true; state.loaded = false; }) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/models.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/models.ts index b43de3a1914d0..3df6bd275cc96 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/models.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/models.ts @@ -5,20 +5,54 @@ * 2.0. */ +import { ErrorToastOptions } from '@kbn/core-notifications-browser'; + import { + EncryptedSyntheticsMonitor, EncryptedSyntheticsSavedMonitor, FetchMonitorManagementListQueryArgs, + SyntheticsMonitor, } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; + export type MonitorListSortField = `${keyof EncryptedSyntheticsSavedMonitor}.keyword` | 'enabled'; -export interface MonitorListPageState { +export interface MonitorFilterState { + tags?: string[]; + monitorTypes?: string[]; + projects?: string[]; + schedules?: string[]; + locations?: string[]; +} + +export interface MonitorListPageState extends MonitorFilterState { query?: string; pageIndex: number; pageSize: number; sortField: MonitorListSortField; sortOrder: NonNullable; - tags?: string[]; - monitorType?: string[]; - locations?: string[]; +} + +interface ToastParams { + message: MessageType; + lifetimeMs: number; + testAttribute?: string; +} + +export interface UpsertMonitorRequest { + configId: string; + monitor: Partial | Partial; + success: ToastParams; + error: ToastParams; + /** + * The effect will perform a quiet refresh of the overview state + * after a successful upsert. The default behavior is to perform the fetch. + */ + shouldQuietFetchAfterSuccess?: boolean; +} + +export interface UpsertMonitorError { + configId: string; + error: IHttpSerializedFetchError; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts index 74f3f62a30ee2..4f1b26dec5142 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/selectors.ts @@ -9,6 +9,7 @@ import { createSelector } from 'reselect'; import { ConfigKey, EncryptedSyntheticsSavedMonitor } from '../../../../../common/runtime_types'; import { SyntheticsAppState } from '../root_reducer'; +import { MonitorFilterState } from './models'; export const selectMonitorListState = (state: SyntheticsAppState) => state.monitorList; export const selectEncryptedSyntheticsSavedMonitors = createSelector( @@ -21,6 +22,15 @@ export const selectEncryptedSyntheticsSavedMonitors = createSelector( created_at: monitor.created_at, })) as EncryptedSyntheticsSavedMonitor[] ); + +export const selectMonitorFiltersAndQueryState = createSelector(selectMonitorListState, (state) => { + const { monitorTypes, tags, locations, projects, schedules }: MonitorFilterState = + state.pageState; + const { query } = state.pageState; + + return { monitorTypes, tags, locations, projects, schedules, query }; +}); + export const selectMonitorUpsertStatuses = (state: SyntheticsAppState) => state.monitorList.monitorUpsertStatuses; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/api.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/api.ts index 5ede7bad9bb9b..69cf734c1592a 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/api.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/api.ts @@ -32,7 +32,9 @@ function toMonitorOverviewQueryArgs( query: pageState.query, tags: pageState.tags, locations: pageState.locations, - monitorType: pageState.monitorType, + projects: pageState.projects, + schedules: pageState.schedules, + monitorTypes: pageState.monitorTypes, sortField: pageState.sortField, sortOrder: pageState.sortOrder, searchFields: [], diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/effects.ts index 3454b0f7c7eee..b0b6a0f467bf1 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/effects.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { takeLatest, takeLeading } from 'redux-saga/effects'; +import { takeLatest, debounce } from 'redux-saga/effects'; import { fetchEffectFactory } from '../utils/fetch_effect'; import { fetchMonitorOverviewAction, @@ -16,7 +16,8 @@ import { import { fetchMonitorOverview, fetchOverviewStatus } from './api'; export function* fetchMonitorOverviewEffect() { - yield takeLeading( + yield debounce( + 300, // Only take the latest while ignoring any intermediate triggers [fetchMonitorOverviewAction.get, quietFetchOverviewAction.get], fetchEffectFactory( fetchMonitorOverview, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/models.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/models.ts index f5454a7d73960..fdc6dcb07df50 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/models.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/models.ts @@ -7,13 +7,11 @@ import { MonitorOverviewResult, OverviewStatusState } from '../../../../../common/runtime_types'; import { IHttpSerializedFetchError } from '../utils/http_error'; +import { MonitorFilterState } from '../monitor_list'; -export interface MonitorOverviewPageState { +export interface MonitorOverviewPageState extends MonitorFilterState { perPage: number; query?: string; - tags?: string[]; - monitorType?: string[]; - locations?: string[]; sortOrder: 'asc' | 'desc'; sortField: string; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts index 6f94e7082d2b7..7cb8cb7858353 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/testing/__mocks__/synthetics_store.mock.ts @@ -67,7 +67,8 @@ export const mockState: SyntheticsAppState = { sortField: `${ConfigKey.NAME}.keyword`, sortOrder: 'asc', tags: undefined, - monitorType: undefined, + monitorTypes: undefined, + projects: undefined, locations: undefined, }, monitorUpsertStatuses: {}, diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts index c428d43babf68..0af05a77269ad 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.test.ts @@ -71,7 +71,9 @@ describe('getSupportedUrlParams', () => { statusFilter: STATUS_FILTER, query: '', locations: [], - monitorType: [], + monitorTypes: [], + projects: [], + schedules: [], tags: [], }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts index 902b296e3db0e..1441a8ca071f3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/utils/url_params/get_supported_url_params.ts @@ -28,9 +28,11 @@ export interface SyntheticsUrlParams { query?: string; tags?: string[]; locations?: string[]; - monitorType?: string[]; + monitorTypes?: string[]; status?: string[]; locationId?: string; + projects?: string[]; + schedules?: string[]; } const { @@ -87,9 +89,11 @@ export const getSupportedUrlParams = (params: { focusConnectorField, query, tags, - monitorType, + monitorTypes, locations, locationId, + projects, + schedules, } = filteredParams; return { @@ -114,8 +118,10 @@ export const getSupportedUrlParams = (params: { focusConnectorField: !!focusConnectorField, query: query || '', tags: tags ? JSON.parse(tags) : [], - monitorType: monitorType ? JSON.parse(monitorType) : [], + monitorTypes: monitorTypes ? JSON.parse(monitorTypes) : [], locations: locations ? JSON.parse(locations) : [], + projects: projects ? JSON.parse(projects) : [], + schedules: schedules ? JSON.parse(schedules) : [], locationId: locationId || undefined, }; }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx index 5cd82233a5890..4866f8cc55e45 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/monitor_fields.test.tsx @@ -113,7 +113,7 @@ describe('', () => { expect(queryByText('URL is required')).not.toBeNull(); }); - it('is reradonly when source type is project', async () => { + it('is readonly when source type is project', async () => { const name = 'monitor name'; const browserFields = { ...defaultBrowserConfig, @@ -127,6 +127,6 @@ describe('', () => { /> ); - expect(getByText('Read only')).toBeInTheDocument(); + expect(getByText(/read-only/)).toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx index 5f287dbbe8b58..1dffbde06576c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/monitor_config/project_readonly_view/readonly_common_fields.tsx @@ -39,7 +39,7 @@ export const ProjectReadonlyCommonFields = ({ title={ } iconType="document" diff --git a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts index 0225eeb08acfd..ba5327d200b46 100644 --- a/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts +++ b/x-pack/plugins/synthetics/server/alert_rules/status_rule/status_rule_executor.ts @@ -153,6 +153,7 @@ export class StatusRuleExecutor { allMonitorsCount: allIds.length, disabledMonitorsCount: allIds.length, projectMonitorsCount, + allIds, }; } diff --git a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts index 4498f09f13101..41f18f5ddd8c6 100644 --- a/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts +++ b/x-pack/plugins/synthetics/server/queries/query_monitor_status.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { intersection } from 'lodash'; import { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; import { SUMMARY_FILTER } from '../../common/constants/client_defaults'; import { UptimeEsClient } from '../legacy_uptime/lib/lib'; @@ -117,7 +118,6 @@ export async function queryMonitorStatus( for await (const response of promises) { response.body.aggregations?.id.buckets.forEach( ({ location, key: queryId }: { location: any; key: string }) => { - const monLocations = monitorLocationsMap?.[queryId]; const locationSummaries = location.buckets.map( ({ status, key: locationName }: { key: string; status: any }) => { const ping = status.hits.hits[0]._source as Ping & { '@timestamp': string }; @@ -125,8 +125,11 @@ export async function queryMonitorStatus( } ) as Array<{ location: string; ping: Ping & { '@timestamp': string } }>; - // discard any locations that are not in the monitorLocationsMap for the given monitor - monLocations?.forEach((monLocation) => { + // discard any locations that are not in the monitorLocationsMap for the given monitor as well as those which are + // in monitorLocationsMap but not in listOfLocations + const monLocations = monitorLocationsMap?.[queryId]; + const monQueriedLocations = intersection(monLocations, listOfLocations); + monQueriedLocations?.forEach((monLocation) => { const locationSummary = locationSummaries.find( (summary) => summary.location === monLocation ); @@ -166,5 +169,5 @@ export async function queryMonitorStatus( } ); } - return { up, down, pending, upConfigs, downConfigs, enabledIds: ids }; + return { up, down, pending, upConfigs, downConfigs, enabledIds: ids, allIds: ids }; } diff --git a/x-pack/plugins/synthetics/server/routes/common.ts b/x-pack/plugins/synthetics/server/routes/common.ts index da8a6b476ceb0..7ac41cfd843ae 100644 --- a/x-pack/plugins/synthetics/server/routes/common.ts +++ b/x-pack/plugins/synthetics/server/routes/common.ts @@ -20,8 +20,10 @@ export const QuerySchema = schema.object({ query: schema.maybe(schema.string()), filter: schema.maybe(schema.string()), tags: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), - monitorType: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + monitorTypes: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), locations: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + projects: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + schedules: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), status: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), fields: schema.maybe(schema.arrayOf(schema.string())), searchAfter: schema.maybe(schema.arrayOf(schema.string())), @@ -51,19 +53,23 @@ export const getMonitors = ( sortOrder, query, tags, - monitorType, + monitorTypes, locations, filter = '', fields, searchAfter, + projects, + schedules, } = request as MonitorsQuery; const filterStr = getMonitorFilters({ filter, - monitorTypes: monitorType, + monitorTypes, tags, locations, serviceLocations: syntheticsService.locations, + projects, + schedules, }); return savedObjectsClient.find({ @@ -85,13 +91,17 @@ export const getMonitorFilters = ({ ports, filter, locations, + projects, monitorTypes, + schedules, serviceLocations, }: { filter?: string; tags?: string | string[]; monitorTypes?: string | string[]; locations?: string | string[]; + projects?: string | string[]; + schedules?: string | string[]; ports?: string | string[]; serviceLocations: ServiceLocations; }) => { @@ -100,8 +110,10 @@ export const getMonitorFilters = ({ return [ filter, getKqlFilter({ field: 'tags', values: tags }), + getKqlFilter({ field: 'project_id', values: projects }), getKqlFilter({ field: 'type', values: monitorTypes }), getKqlFilter({ field: 'locations.id', values: locationFilter }), + getKqlFilter({ field: 'schedule.number', values: schedules }), ] .filter((f) => !!f) .join(' AND '); @@ -129,7 +141,7 @@ export const getKqlFilter = ({ } if (Array.isArray(values)) { - return `${fieldKey}:"${values.join(`" ${operator} ${fieldKey}:"`)}"`; + return ` (${fieldKey}:"${values.join(`" ${operator} ${fieldKey}:"`)}" )`; } return `${fieldKey}:"${values}"`; @@ -143,7 +155,7 @@ const parseLocationFilter = (serviceLocations: ServiceLocations, locations?: str if (Array.isArray(locations)) { return locations .map((loc) => findLocationItem(loc, serviceLocations)?.id ?? '') - .filter((val) => !val); + .filter((val) => !!val); } return findLocationItem(locations, serviceLocations)?.id ?? ''; @@ -159,15 +171,18 @@ export const findLocationItem = (query: string, locations: ServiceLocations) => * @param monitorQuery { MonitorsQuery } */ export const isMonitorsQueryFiltered = (monitorQuery: MonitorsQuery) => { - const { query, tags, monitorType, locations, status, filter } = monitorQuery; + const { query, tags, monitorTypes, locations, status, filter, projects, schedules } = + monitorQuery; return ( !!query || !!filter || !!locations?.length || - !!monitorType?.length || + !!monitorTypes?.length || !!tags?.length || - !!status?.length + !!status?.length || + !!projects?.length || + !!schedules?.length ); }; diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts index 0dca273c2613e..2f3b4b2595dc9 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/delete_monitor.ts @@ -10,6 +10,7 @@ import { SavedObjectsClientContract, SavedObjectsErrorHelpers, } from '@kbn/core/server'; +import { deletePermissionError } from '../../synthetics_service/private_location/synthetics_private_location'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; import { ConfigKey, @@ -94,8 +95,20 @@ export const deleteMonitor = async ({ savedObjectsClient, server ); + + const { locations } = monitor.attributes; + + const hasPrivateLocation = locations.filter((loc) => !loc.isServiceManaged); + + if (hasPrivateLocation.length > 0) { + await syntheticsMonitorClient.privateLocationAPI.checkPermissions( + request, + deletePermissionError(monitor.attributes.name) + ); + } + try { - const { id: spaceId } = await server.spaces.spacesService.getActiveSpace(request); + const spaceId = server.spaces.spacesService.getSpaceId(request); const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors( [ { diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts index 9b328e017b65a..e2edf19d3ff31 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/get_monitor.ts @@ -6,14 +6,26 @@ */ import { schema } from '@kbn/config-schema'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { getAllMonitors } from '../../saved_objects/synthetics_monitor/get_all_monitors'; import { isStatusEnabled } from '../../../common/runtime_types/monitor_management/alert_config'; -import { ConfigKey, MonitorOverviewItem, SyntheticsMonitor } from '../../../common/runtime_types'; +import { + ConfigKey, + EncryptedSyntheticsMonitor, + MonitorOverviewItem, +} from '../../../common/runtime_types'; import { UMServerLibs } from '../../legacy_uptime/lib/lib'; import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; import { API_URLS, SYNTHETICS_API_URLS } from '../../../common/constants'; import { syntheticsMonitorType } from '../../legacy_uptime/lib/saved_objects/synthetics_monitor'; import { getMonitorNotFoundResponse } from '../synthetics_service/service_errors'; -import { getMonitors, isMonitorsQueryFiltered, QuerySchema, SEARCH_FIELDS } from '../common'; +import { + getMonitorFilters, + getMonitors, + isMonitorsQueryFiltered, + MonitorsQuery, + QuerySchema, + SEARCH_FIELDS, +} from '../common'; export const getSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', @@ -89,13 +101,24 @@ export const getSyntheticsMonitorOverviewRoute: SyntheticsRestApiRouteFactory = validate: { query: QuerySchema, }, - handler: async ({ request, savedObjectsClient }): Promise => { - const { sortField, sortOrder, query } = request.query; - const finder = savedObjectsClient.createPointInTimeFinder({ - type: syntheticsMonitorType, - sortField: sortField === 'status' ? `${ConfigKey.NAME}.keyword` : sortField, + handler: async ({ request, savedObjectsClient, syntheticsMonitorClient }): Promise => { + const { + sortField, sortOrder, - perPage: 1000, + query, + locations: queriedLocations, + } = request.query as MonitorsQuery; + + const filtersStr = getMonitorFilters({ + ...request.query, + serviceLocations: syntheticsMonitorClient.syntheticsService.locations, + }); + + const allMonitorConfigs = await getAllMonitors({ + sortOrder, + filter: filtersStr, + soClient: savedObjectsClient, + sortField: sortField === 'status' ? `${ConfigKey.NAME}.keyword` : sortField, search: query ? `${query}*` : undefined, searchFields: SEARCH_FIELDS, }); @@ -104,29 +127,13 @@ export const getSyntheticsMonitorOverviewRoute: SyntheticsRestApiRouteFactory = let total = 0; const allMonitors: MonitorOverviewItem[] = []; - for await (const result of finder.find()) { - /* collect all monitor ids for use - * in filtering overview requests */ - result.saved_objects.forEach((monitor) => { - const id = monitor.attributes[ConfigKey.MONITOR_QUERY_ID]; - const configId = monitor.attributes[ConfigKey.CONFIG_ID]; - allMonitorIds.push(configId); - - /* for each location, add a config item */ - const locations = monitor.attributes[ConfigKey.LOCATIONS]; - locations.forEach((location) => { - const config = { - id, - configId, - name: monitor.attributes[ConfigKey.NAME], - location, - isEnabled: monitor.attributes[ConfigKey.ENABLED], - isStatusAlertEnabled: isStatusEnabled(monitor.attributes[ConfigKey.ALERT_CONFIG]), - }; - allMonitors.push(config); - total++; - }); - }); + for (const { attributes } of allMonitorConfigs) { + const configId = attributes[ConfigKey.CONFIG_ID]; + allMonitorIds.push(configId); + + const monitorConfigsPerLocation = getOverviewConfigsPerLocation(attributes, queriedLocations); + allMonitors.push(...monitorConfigsPerLocation); + total += monitorConfigsPerLocation.length; } return { @@ -136,3 +143,34 @@ export const getSyntheticsMonitorOverviewRoute: SyntheticsRestApiRouteFactory = }; }, }); + +function getOverviewConfigsPerLocation( + attributes: EncryptedSyntheticsMonitor, + queriedLocations: string | string[] | undefined +) { + const id = attributes[ConfigKey.MONITOR_QUERY_ID]; + const configId = attributes[ConfigKey.CONFIG_ID]; + + /* for each location, add a config item */ + const locations = attributes[ConfigKey.LOCATIONS]; + const queriedLocationsArray = + queriedLocations && !Array.isArray(queriedLocations) ? [queriedLocations] : queriedLocations; + + /* exclude nob matching locations if location filter is present */ + const filteredLocations = queriedLocationsArray?.length + ? locations.filter( + (loc) => + (loc.label && queriedLocationsArray.includes(loc.label)) || + queriedLocationsArray.includes(loc.id) + ) + : locations; + + return filteredLocations.map((location) => ({ + id, + configId, + name: attributes[ConfigKey.NAME], + location, + isEnabled: attributes[ConfigKey.ENABLED], + isStatusAlertEnabled: isStatusEnabled(attributes[ConfigKey.ALERT_CONFIG]), + })); +} diff --git a/x-pack/plugins/synthetics/server/routes/status/current_status.test.ts b/x-pack/plugins/synthetics/server/routes/status/current_status.test.ts index d2c08d495973c..b86d611edf32f 100644 --- a/x-pack/plugins/synthetics/server/routes/status/current_status.test.ts +++ b/x-pack/plugins/synthetics/server/routes/status/current_status.test.ts @@ -178,6 +178,7 @@ describe('current status route', () => { pending: 0, down: 1, enabledIds: ['id1', 'id2'], + allIds: ['id1', 'id2'], up: 2, upConfigs: { 'id1-Asia/Pacific - Japan': { @@ -321,18 +322,24 @@ describe('current status route', () => { * * The expectation here is we will send the test client two separate "requests", one for each of the two IDs. */ + const concernedLocations = [ + 'Asia/Pacific - Japan', + 'Europe - Germany', + 'Asia/Pacific - Japan', + ]; expect( await queryMonitorStatus( uptimeEsClient, - times(10000).map((n) => 'Europe - Germany' + n), + [...concernedLocations, ...times(9997).map((n) => 'Europe - Germany' + n)], { from: 2500, to: 'now' }, ['id1', 'id2'], - { id1: ['Asia/Pacific - Japan'], id2: ['Europe - Germany', 'Asia/Pacific - Japan'] } + { id1: [concernedLocations[0]], id2: [concernedLocations[1], concernedLocations[2]] } ) ).toEqual({ pending: 0, down: 1, enabledIds: ['id1', 'id2'], + allIds: ['id1', 'id2'], up: 2, upConfigs: { 'id1-Asia/Pacific - Japan': { diff --git a/x-pack/plugins/synthetics/server/routes/status/current_status.ts b/x-pack/plugins/synthetics/server/routes/status/current_status.ts index a2554cffc652c..48874fb83f675 100644 --- a/x-pack/plugins/synthetics/server/routes/status/current_status.ts +++ b/x-pack/plugins/synthetics/server/routes/status/current_status.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { intersection } from 'lodash'; import datemath, { Unit } from '@kbn/datemath'; import { SavedObjectsClientContract } from '@kbn/core/server'; import { @@ -19,7 +20,7 @@ import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes'; import { UptimeEsClient } from '../../legacy_uptime/lib/lib'; import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client'; import { ConfigKey } from '../../../common/runtime_types'; -import { QuerySchema, MonitorsQuery } from '../common'; +import { QuerySchema, MonitorsQuery, getMonitorFilters } from '../common'; /** * Helper function that converts a monitor's schedule to a value to use to generate @@ -46,7 +47,7 @@ export async function getStatus( syntheticsMonitorClient: SyntheticsMonitorClient, params: MonitorsQuery ) { - const { query } = params; + const { query, locations: queryLocations } = params; /** * Walk through all monitor saved objects, bucket IDs by disabled/enabled status. * @@ -54,9 +55,14 @@ export async function getStatus( * latest ping for all enabled monitors. */ + const filtersStr = getMonitorFilters({ + ...params, + serviceLocations: syntheticsMonitorClient.syntheticsService.locations, + }); const allMonitors = await getAllMonitors({ soClient, search: query ? `${query}*` : undefined, + filter: filtersStr, fields: [ ConfigKey.ENABLED, ConfigKey.LOCATIONS, @@ -67,6 +73,7 @@ export async function getStatus( }); const { + allIds, enabledIds, disabledCount, maxPeriod, @@ -76,15 +83,23 @@ export async function getStatus( projectMonitorsCount, } = await processMonitors(allMonitors, server, soClient, syntheticsMonitorClient); + // Account for locations filter + const queryLocationsArray = + queryLocations && !Array.isArray(queryLocations) ? [queryLocations] : queryLocations; + const listOfLocationAfterFilter = queryLocationsArray + ? intersection(listOfLocations, queryLocationsArray) + : listOfLocations; + const { up, down, pending, upConfigs, downConfigs } = await queryMonitorStatus( uptimeEsClient, - listOfLocations, + listOfLocationAfterFilter, { from: maxPeriod, to: 'now' }, enabledIds, monitorLocationMap ); return { + allIds, allMonitorsCount: allMonitors.length, disabledMonitorsCount, projectMonitorsCount, diff --git a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts index d2c28d811eb54..4bb52d9090906 100644 --- a/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts +++ b/x-pack/plugins/synthetics/server/saved_objects/synthetics_monitor/get_all_monitors.ts @@ -27,12 +27,15 @@ export const getAllMonitors = async ({ soClient, search, fields, + filter, sortField, sortOrder, + searchFields, }: { soClient: SavedObjectsClientContract; search?: string; -} & Pick) => { + filter?: string; +} & Pick) => { const finder = soClient.createPointInTimeFinder({ type: syntheticsMonitorType, perPage: 1000, @@ -40,6 +43,8 @@ export const getAllMonitors = async ({ sortField, sortOrder, fields, + filter, + searchFields, }); const hits: Array> = []; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts index 160bd62de1595..02a1828c1f56c 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/private_location/synthetics_private_location.ts @@ -337,19 +337,12 @@ export class SyntheticsPrivateLocation { policyIdsToDelete.push(this.getPolicyId(config, privateLocation.id, spaceId)); } catch (e) { this.server.logger.error(e); - throw new Error( - `Unable to delete Synthetics package policy for monitor ${ - config[ConfigKey.NAME] - } with private location ${privateLocation.label}` - ); + throw new Error(deletePolicyError(config[ConfigKey.NAME], privateLocation.label)); } } } if (policyIdsToDelete.length > 0) { - await this.checkPermissions( - request, - `Unable to delete Synthetics package policy for monitor. Fleet write permissions are needed to use Synthetics private locations.` - ); + await this.checkPermissions(request, deletePermissionError()); await this.deletePolicyBulk(policyIdsToDelete, soClient); } } @@ -367,6 +360,10 @@ export class SyntheticsPrivateLocation { } } -const deletePermissionError = (name: string) => { +export const deletePermissionError = (name?: string) => { return `Unable to delete Synthetics package policy for monitor ${name}. Fleet write permissions are needed to use Synthetics private locations.`; }; + +const deletePolicyError = (name: string, location?: string) => { + return `Unable to delete Synthetics package policy for monitor ${name} with private location ${location}`; +}; diff --git a/x-pack/plugins/synthetics/tsconfig.json b/x-pack/plugins/synthetics/tsconfig.json index e752376f4e908..384edfb5f7522 100644 --- a/x-pack/plugins/synthetics/tsconfig.json +++ b/x-pack/plugins/synthetics/tsconfig.json @@ -71,6 +71,7 @@ "@kbn/core-elasticsearch-server", "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-saved-objects-server", + "@kbn/shared-ux-prompt-not-found", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.test.ts index 8edb6b5397209..1a9aeb53b7652 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.test.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.test.ts @@ -17,5 +17,9 @@ describe('unwrapValue()', () => { expect( unwrapValue({ fields: { [RawIndicatorFieldId.Type]: ['ip'] } }, RawIndicatorFieldId.Type) ).toEqual('ip'); + + expect( + unwrapValue({ fields: { [RawIndicatorFieldId.Type]: [{}] } }, RawIndicatorFieldId.Type) + ).toEqual(null); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.ts index 8d3ef63ec9f0b..babd540d1bb29 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/unwrap_value.ts @@ -5,20 +5,24 @@ * 2.0. */ -import { Indicator, RawIndicatorFieldId } from '../../../../common/types/indicator'; +type IndicatorLike = Record<'fields', Record> | null | undefined; /** * Unpacks field value from raw indicator fields. Will return null if fields are missing entirely * or there is no record for given `fieldId` */ -export const unwrapValue = ( - indicator: Partial | null | undefined, - fieldId: RawIndicatorFieldId -): T | null => { +export const unwrapValue = (indicator: IndicatorLike, fieldId: string): T | null => { if (!indicator) { return null; } - const valueArray = indicator.fields?.[fieldId]; - return Array.isArray(valueArray) ? (valueArray[0] as T) : null; + const fieldValues = indicator.fields?.[fieldId]; + + if (!Array.isArray(fieldValues)) { + return null; + } + + const firstValue = fieldValues[0]; + + return typeof firstValue === 'object' ? null : (firstValue as T); }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.test.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.test.ts index aaa2373f868bf..ea00d5eba5b5f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.test.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.test.ts @@ -5,20 +5,25 @@ * 2.0. */ -import { Filter } from '@kbn/es-query'; +import { Filter, PhraseFilter } from '@kbn/es-query'; import { FilterIn, FilterOut, updateFiltersArray } from '.'; describe('updateFiltersArray', () => { it('should add new filter', () => { - const existingFilters: Filter[] = []; + const existingFilters: PhraseFilter[] = []; const key: string = 'key'; const value: string = 'value'; const filterType: boolean = FilterIn; - const newFilters = updateFiltersArray(existingFilters, key, value, filterType); + const newFilters = updateFiltersArray( + existingFilters, + key, + value, + filterType + ) as PhraseFilter[]; expect(newFilters).toHaveLength(1); expect(newFilters[0].meta.key).toEqual(key); - expect(newFilters[0].meta.params.query).toEqual(value); + expect(newFilters[0].meta.params?.query).toEqual(value); expect(newFilters[0].meta.negate).toEqual(!filterType); }); @@ -40,10 +45,15 @@ describe('updateFiltersArray', () => { ]; const filterType: boolean = FilterOut; - const newFilters = updateFiltersArray(existingFilters, key, value, filterType); + const newFilters = updateFiltersArray( + existingFilters, + key, + value, + filterType + ) as PhraseFilter[]; expect(newFilters).toHaveLength(1); expect(newFilters[0].meta.key).toEqual(key); - expect(newFilters[0].meta.params.query).toEqual(value); + expect(newFilters[0].meta.params?.query).toEqual(value); expect(newFilters[0].meta.negate).toEqual(!filterType); }); @@ -64,11 +74,15 @@ describe('updateFiltersArray', () => { }, ]; const filterType: boolean = FilterIn; - - const newFilters = updateFiltersArray(existingFilters, key, value, filterType); + const newFilters = updateFiltersArray( + existingFilters, + key, + value, + filterType + ) as PhraseFilter[]; expect(newFilters).toHaveLength(1); expect(newFilters[0].meta.key).toEqual(key); - expect(newFilters[0].meta.params.query).toEqual(value); + expect(newFilters[0].meta.params?.query).toEqual(value); expect(newFilters[0].meta.negate).toEqual(!filterType); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.ts index fe4c5cb88e06e..1bb1661a4e750 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/utils/filter.ts @@ -41,7 +41,13 @@ const filterExistsInFiltersArray = ( key: string, value: string ): Filter | undefined => - filters.find((f: Filter) => f.meta.key === key && f.meta.params.query === value); + filters.find( + (f: Filter) => + f.meta.key === key && + typeof f.meta.params === 'object' && + 'query' in f.meta.params && + f.meta.params?.query === value + ); /** * Returns true if the filter exists and should be removed, false otherwise (depending on a FilterIn or FilterOut action) diff --git a/x-pack/plugins/transform/common/types/date_picker.ts b/x-pack/plugins/transform/common/types/date_picker.ts new file mode 100644 index 0000000000000..09b5d0cba83be --- /dev/null +++ b/x-pack/plugins/transform/common/types/date_picker.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 interface TimeRangeMs { + from: number; + to: number; +} diff --git a/x-pack/plugins/transform/public/app/common/data_grid.test.ts b/x-pack/plugins/transform/public/app/common/data_grid.test.ts index 31f0a2bc688d8..0cb2ed18c58f7 100644 --- a/x-pack/plugins/transform/public/app/common/data_grid.test.ts +++ b/x-pack/plugins/transform/public/app/common/data_grid.test.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { getPreviewTransformRequestBody, SimpleQuery } from '.'; +import type { DataView } from '@kbn/data-views-plugin/common'; -import { getIndexDevConsoleStatement, getPivotPreviewDevConsoleStatement } from './data_grid'; +import { getPreviewTransformRequestBody, SimpleQuery } from '.'; +import { getIndexDevConsoleStatement, getTransformPreviewDevConsoleStatement } from './data_grid'; describe('Transform: Data Grid', () => { - test('getPivotPreviewDevConsoleStatement()', () => { + test('getTransformPreviewDevConsoleStatement()', () => { const query: SimpleQuery = { query_string: { query: '*', @@ -18,26 +19,30 @@ describe('Transform: Data Grid', () => { }, }; - const request = getPreviewTransformRequestBody('the-index-pattern-title', query, { - pivot: { - group_by: { - 'the-group-by-agg-name': { - terms: { - field: 'the-group-by-field', + const request = getPreviewTransformRequestBody( + { getIndexPattern: () => 'the-index-pattern-title' } as DataView, + query, + { + pivot: { + group_by: { + 'the-group-by-agg-name': { + terms: { + field: 'the-group-by-field', + }, }, }, - }, - aggregations: { - 'the-agg-agg-name': { - avg: { - field: 'the-agg-field', + aggregations: { + 'the-agg-agg-name': { + avg: { + field: 'the-agg-field', + }, }, }, }, - }, - }); + } + ); - const pivotPreviewDevConsoleStatement = getPivotPreviewDevConsoleStatement(request); + const pivotPreviewDevConsoleStatement = getTransformPreviewDevConsoleStatement(request); expect(pivotPreviewDevConsoleStatement).toBe(`POST _transform/_preview { diff --git a/x-pack/plugins/transform/public/app/common/data_grid.ts b/x-pack/plugins/transform/public/app/common/data_grid.ts index 43d2b27f13cf9..c6a740787e2b1 100644 --- a/x-pack/plugins/transform/public/app/common/data_grid.ts +++ b/x-pack/plugins/transform/public/app/common/data_grid.ts @@ -7,15 +7,17 @@ import type { PostTransformsPreviewRequestSchema } from '../../../common/api_schemas/transforms'; -import { PivotQuery } from './request'; +import { TransformConfigQuery } from './request'; export const INIT_MAX_COLUMNS = 20; -export const getPivotPreviewDevConsoleStatement = (request: PostTransformsPreviewRequestSchema) => { +export const getTransformPreviewDevConsoleStatement = ( + request: PostTransformsPreviewRequestSchema +) => { return `POST _transform/_preview\n${JSON.stringify(request, null, 2)}\n`; }; -export const getIndexDevConsoleStatement = (query: PivotQuery, dataViewTitle: string) => { +export const getIndexDevConsoleStatement = (query: TransformConfigQuery, dataViewTitle: string) => { return `GET ${dataViewTitle}/_search\n${JSON.stringify( { query, diff --git a/x-pack/plugins/transform/public/app/common/index.ts b/x-pack/plugins/transform/public/app/common/index.ts index 1f397ee4285ef..c7656974ec569 100644 --- a/x-pack/plugins/transform/public/app/common/index.ts +++ b/x-pack/plugins/transform/public/app/common/index.ts @@ -8,7 +8,7 @@ export { isAggName } from './aggregations'; export { getIndexDevConsoleStatement, - getPivotPreviewDevConsoleStatement, + getTransformPreviewDevConsoleStatement, INIT_MAX_COLUMNS, } from './data_grid'; export type { EsDoc, EsDocSource } from './fields'; @@ -64,12 +64,12 @@ export { pivotGroupByFieldSupport, PIVOT_SUPPORTED_GROUP_BY_AGGS, } from './pivot_group_by'; -export type { PivotQuery, SimpleQuery } from './request'; +export type { TransformConfigQuery, SimpleQuery } from './request'; export { defaultQuery, getPreviewTransformRequestBody, getCreateTransformRequestBody, - getPivotQuery, + getTransformConfigQuery, getRequestPayload, isDefaultQuery, isMatchAllQuery, diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts index 2c4415c56c466..60ad397f6f30a 100644 --- a/x-pack/plugins/transform/public/app/common/request.test.ts +++ b/x-pack/plugins/transform/public/app/common/request.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { DataView } from '@kbn/data-views-plugin/common'; + import { PIVOT_SUPPORTED_AGGS } from '../../../common/types/pivot_aggs'; import { PivotGroupByConfig } from '.'; @@ -19,19 +21,19 @@ import { getPreviewTransformRequestBody, getCreateTransformRequestBody, getCreateTransformSettingsRequestBody, - getPivotQuery, + getTransformConfigQuery, getMissingBucketConfig, getRequestPayload, isDefaultQuery, isMatchAllQuery, isSimpleQuery, matchAllQuery, - PivotQuery, + type TransformConfigQuery, } from './request'; import { LatestFunctionConfigUI } from '../../../common/types/transform'; import type { RuntimeField } from '@kbn/data-views-plugin/common'; -const simpleQuery: PivotQuery = { query_string: { query: 'airline:AAL' } }; +const simpleQuery: TransformConfigQuery = { query_string: { query: 'airline:AAL' } }; const groupByTerms: PivotGroupByConfig = { agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, @@ -62,12 +64,12 @@ describe('Transform: Common', () => { test('isDefaultQuery()', () => { expect(isDefaultQuery(defaultQuery)).toBe(true); - expect(isDefaultQuery(matchAllQuery)).toBe(false); + expect(isDefaultQuery(matchAllQuery)).toBe(true); expect(isDefaultQuery(simpleQuery)).toBe(false); }); - test('getPivotQuery()', () => { - const query = getPivotQuery('the-query'); + test('getTransformConfigQuery()', () => { + const query = getTransformConfigQuery('the-query'); expect(query).toEqual({ query_string: { @@ -78,14 +80,18 @@ describe('Transform: Common', () => { }); test('getPreviewTransformRequestBody()', () => { - const query = getPivotQuery('the-query'); + const query = getTransformConfigQuery('the-query'); - const request = getPreviewTransformRequestBody('the-data-view-title', query, { - pivot: { - aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, - group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } }, - }, - }); + const request = getPreviewTransformRequestBody( + { getIndexPattern: () => 'the-data-view-title' } as DataView, + query, + { + pivot: { + aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, + group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } }, + }, + } + ); expect(request).toEqual({ pivot: { @@ -100,13 +106,17 @@ describe('Transform: Common', () => { }); test('getPreviewTransformRequestBody() with comma-separated index pattern', () => { - const query = getPivotQuery('the-query'); - const request = getPreviewTransformRequestBody('the-data-view-title,the-other-title', query, { - pivot: { - aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, - group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } }, - }, - }); + const query = getTransformConfigQuery('the-query'); + const request = getPreviewTransformRequestBody( + { getIndexPattern: () => 'the-data-view-title,the-other-title' } as DataView, + query, + { + pivot: { + aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, + group_by: { 'the-group-by-agg-name': { terms: { field: 'the-group-by-field' } } }, + }, + } + ); expect(request).toEqual({ pivot: { @@ -172,9 +182,9 @@ describe('Transform: Common', () => { }); test('getPreviewTransformRequestBody() with missing_buckets config', () => { - const query = getPivotQuery('the-query'); + const query = getTransformConfigQuery('the-query'); const request = getPreviewTransformRequestBody( - 'the-data-view-title', + { getIndexPattern: () => 'the-data-view-title' } as DataView, query, getRequestPayload([aggsAvg], [{ ...groupByTerms, ...{ missing_bucket: true } }]) ); @@ -194,11 +204,12 @@ describe('Transform: Common', () => { }); test('getCreateTransformRequestBody() skips default values', () => { - const pivotState: StepDefineExposedState = { + const transformConfigState: StepDefineExposedState = { aggList: { 'the-agg-name': aggsAvg }, groupByList: { 'the-group-by-name': groupByTerms }, isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, + isDatePickerApplyEnabled: false, sourceConfigUpdated: false, searchLanguage: 'kuery', searchString: 'the-query', @@ -239,8 +250,8 @@ describe('Transform: Common', () => { }; const request = getCreateTransformRequestBody( - 'the-data-view-title', - pivotState, + { getIndexPattern: () => 'the-data-view-title' } as DataView, + transformConfigState, transformDetailsState ); @@ -278,6 +289,7 @@ describe('Transform: Common', () => { groupByList: { 'the-group-by-name': groupByTerms }, isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, + isDatePickerApplyEnabled: false, sourceConfigUpdated: false, searchLanguage: 'kuery', searchString: 'the-query', @@ -319,7 +331,7 @@ describe('Transform: Common', () => { }; const request = getCreateTransformRequestBody( - 'the-data-view-title', + { getIndexPattern: () => 'the-data-view-title' } as DataView, pivotState, transformDetailsState ); diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index c66618df209f7..42162498f3f3c 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -8,6 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { DataView } from '@kbn/data-views-plugin/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; import { DEFAULT_CONTINUOUS_MODE_DELAY, @@ -47,9 +48,15 @@ export interface SimpleQuery { }; } -export type PivotQuery = SimpleQuery | SavedSearchQuery; +export interface FilterBasedSimpleQuery { + bool: { + filter: [SimpleQuery]; + }; +} + +export type TransformConfigQuery = FilterBasedSimpleQuery | SimpleQuery | SavedSearchQuery; -export function getPivotQuery(search: string | SavedSearchQuery): PivotQuery { +export function getTransformConfigQuery(search: string | SavedSearchQuery): TransformConfigQuery { if (typeof search === 'string') { return { query_string: { @@ -66,6 +73,16 @@ export function isSimpleQuery(arg: unknown): arg is SimpleQuery { return isPopulatedObject(arg, ['query_string']); } +export function isFilterBasedSimpleQuery(arg: unknown): arg is FilterBasedSimpleQuery { + return ( + isPopulatedObject(arg, ['bool']) && + isPopulatedObject(arg.bool, ['filter']) && + Array.isArray(arg.bool.filter) && + arg.bool.filter.length === 1 && + isSimpleQuery(arg.bool.filter[0]) + ); +} + export const matchAllQuery = { match_all: {} }; export function isMatchAllQuery(query: unknown): boolean { return ( @@ -76,9 +93,14 @@ export function isMatchAllQuery(query: unknown): boolean { ); } -export const defaultQuery: PivotQuery = { query_string: { query: '*' } }; -export function isDefaultQuery(query: PivotQuery): boolean { - return isSimpleQuery(query) && query.query_string.query === '*'; +export const defaultQuery: TransformConfigQuery = { query_string: { query: '*' } }; +export function isDefaultQuery(query: TransformConfigQuery): boolean { + return ( + isMatchAllQuery(query) || + (isSimpleQuery(query) && query.query_string.query === '*') || + (isFilterBasedSimpleQuery(query) && + (query.bool.filter[0].query_string.query === '*' || isMatchAllQuery(query.bool.filter[0]))) + ); } export function getCombinedRuntimeMappings( @@ -171,17 +193,36 @@ export const getRequestPayload = ( }; export function getPreviewTransformRequestBody( - dataViewTitle: DataView['title'], - query: PivotQuery, - partialRequest?: StepDefineExposedState['previewRequest'] | undefined, - runtimeMappings?: StepDefineExposedState['runtimeMappings'] + dataView: DataView, + transformConfigQuery: TransformConfigQuery, + partialRequest?: StepDefineExposedState['previewRequest'], + runtimeMappings?: StepDefineExposedState['runtimeMappings'], + timeRangeMs?: StepDefineExposedState['timeRangeMs'] ): PostTransformsPreviewRequestSchema { + const dataViewTitle = dataView.getIndexPattern(); const index = dataViewTitle.split(',').map((name: string) => name.trim()); + const hasValidTimeField = dataView.timeFieldName !== undefined && dataView.timeFieldName !== ''; + + const baseFilterCriteria = buildBaseFilterCriteria( + dataView.timeFieldName, + timeRangeMs?.from, + timeRangeMs?.to, + isDefaultQuery(transformConfigQuery) ? undefined : transformConfigQuery + ); + + const queryWithBaseFilterCriteria = { + bool: { + filter: baseFilterCriteria, + }, + }; + + const query = hasValidTimeField ? queryWithBaseFilterCriteria : transformConfigQuery; + return { source: { index, - ...(!isDefaultQuery(query) && !isMatchAllQuery(query) ? { query } : {}), + ...(isDefaultQuery(query) ? {} : { query }), ...(isPopulatedObject(runtimeMappings) ? { runtime_mappings: runtimeMappings } : {}), }, ...(partialRequest ?? {}), @@ -212,15 +253,18 @@ export const getCreateTransformSettingsRequestBody = ( }; export const getCreateTransformRequestBody = ( - dataViewTitle: DataView['title'], - pivotState: StepDefineExposedState, + dataView: DataView, + transformConfigState: StepDefineExposedState, transformDetailsState: StepDetailsExposedState ): PutTransformsPivotRequestSchema | PutTransformsLatestRequestSchema => ({ ...getPreviewTransformRequestBody( - dataViewTitle, - getPivotQuery(pivotState.searchQuery), - pivotState.previewRequest, - pivotState.runtimeMappings + dataView, + getTransformConfigQuery(transformConfigState.searchQuery), + transformConfigState.previewRequest, + transformConfigState.runtimeMappings, + transformConfigState.isDatePickerApplyEnabled && transformConfigState.timeRangeMs + ? transformConfigState.timeRangeMs + : undefined ), // conditionally add optional description ...(transformDetailsState.transformDescription !== '' diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts index 22c314c89850a..d4fbf1c77d054 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts @@ -10,6 +10,9 @@ import { useEffect, useMemo, useState } from 'react'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { EuiDataGridColumn } from '@elastic/eui'; +import { buildBaseFilterCriteria } from '@kbn/ml-query-utils'; + +import type { TimeRangeMs } from '../../../common/types/date_picker'; import { isEsSearchResponse, isFieldHistogramsResponseSchema, @@ -19,21 +22,23 @@ import { isKeywordDuplicate, removeKeywordPostfix, } from '../../../common/utils/field_utils'; -import type { EsSorting, UseIndexDataReturnType } from '../../shared_imports'; - import { getErrorMessage } from '../../../common/utils/errors'; -import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common'; -import { SearchItems } from './use_search_items'; -import { useApi } from './use_api'; +import { isRuntimeMappings } from '../../../common/shared_imports'; + +import type { EsSorting, UseIndexDataReturnType } from '../../shared_imports'; +import { isDefaultQuery, matchAllQuery, TransformConfigQuery } from '../common'; import { useAppDependencies, useToastNotifications } from '../app_dependencies'; import type { StepDefineExposedState } from '../sections/create_transform/components/step_define/common'; -import { isRuntimeMappings } from '../../../common/shared_imports'; + +import { SearchItems } from './use_search_items'; +import { useApi } from './use_api'; export const useIndexData = ( dataView: SearchItems['dataView'], - query: PivotQuery, - combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'] + query: TransformConfigQuery, + combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'], + timeRangeMs?: TimeRangeMs ): UseIndexDataReturnType => { const indexPattern = useMemo(() => dataView.getIndexPattern(), [dataView]); @@ -55,6 +60,24 @@ export const useIndexData = ( const [dataViewFields, setDataViewFields] = useState(); + const baseFilterCriteria = buildBaseFilterCriteria( + dataView.timeFieldName, + timeRangeMs?.from, + timeRangeMs?.to, + query + ); + + const defaultQuery = useMemo( + () => (timeRangeMs && dataView.timeFieldName ? baseFilterCriteria[0] : matchAllQuery), + [baseFilterCriteria, dataView, timeRangeMs] + ); + + const queryWithBaseFilterCriteria = { + bool: { + filter: baseFilterCriteria, + }, + }; + // Fetch 500 random documents to determine populated fields. // This is a workaround to avoid passing potentially thousands of unpopulated fields // (for example, as part of filebeat/metricbeat/ECS based indices) @@ -70,7 +93,7 @@ export const useIndexData = ( _source: false, query: { function_score: { - query: { match_all: {} }, + query: defaultQuery, random_score: {}, }, }, @@ -106,7 +129,7 @@ export const useIndexData = ( useEffect(() => { fetchDataGridSampleDocuments(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [timeRangeMs]); const columns: EuiDataGridColumn[] = useMemo(() => { if (typeof dataViewFields === 'undefined') { @@ -165,7 +188,7 @@ export const useIndexData = ( resetPagination(); // custom comparison // eslint-disable-next-line react-hooks/exhaustive-deps - }, [JSON.stringify(query)]); + }, [JSON.stringify([query, timeRangeMs])]); const fetchDataGridData = async function () { setErrorMessage(''); @@ -181,8 +204,7 @@ export const useIndexData = ( body: { fields: ['*'], _source: false, - // Instead of using the default query (`*`), fall back to a more efficient `match_all` query. - query: isDefaultQuery(query) ? matchAllQuery : query, + query: isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, from: pagination.pageIndex * pagination.pageSize, size: pagination.pageSize, ...(Object.keys(sort).length > 0 ? { sort } : {}), @@ -236,7 +258,7 @@ export const useIndexData = ( type: getFieldType(cT.schema), }; }), - isDefaultQuery(query) ? matchAllQuery : query, + isDefaultQuery(query) ? defaultQuery : queryWithBaseFilterCriteria, combinedRuntimeMappings ); @@ -263,7 +285,14 @@ export const useIndexData = ( }, [ indexPattern, // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify([query, pagination, sortingColumns, dataViewFields, combinedRuntimeMappings]), + JSON.stringify([ + query, + pagination, + sortingColumns, + dataViewFields, + combinedRuntimeMappings, + timeRangeMs, + ]), ]); useEffect(() => { @@ -276,7 +305,7 @@ export const useIndexData = ( chartsVisible, indexPattern, // eslint-disable-next-line react-hooks/exhaustive-deps - JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings]), + JSON.stringify([query, dataGrid.visibleColumns, combinedRuntimeMappings, timeRangeMs]), ]); const renderCellValue = useRenderCellValue(dataView, pagination, tableItems); diff --git a/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts b/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts index cd24d092f754c..51e6fcaf469e7 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_search_items/use_search_items.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { i18n } from '@kbn/i18n'; @@ -27,6 +27,13 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => { const [searchItems, setSearchItems] = useState(undefined); + const isMounted = useRef(true); + useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); + async function fetchSavedObject(id: string) { let fetchedDataView; let fetchedSavedSearch; @@ -44,7 +51,7 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => { spaces: appDeps.spaces, }); - if (fetchedSavedSearch?.sharingSavedObjectProps?.errorJSON) { + if (isMounted.current && fetchedSavedSearch?.sharingSavedObjectProps?.errorJSON) { setError(await getSavedSearchUrlConflictMessage(fetchedSavedSearch)); return; } @@ -52,17 +59,19 @@ export const useSearchItems = (defaultSavedObjectId: string | undefined) => { // Just let fetchedSavedSearch stay undefined in case it doesn't exist. } - if (!isDataView(fetchedDataView) && fetchedSavedSearch === undefined) { - setError( - i18n.translate('xpack.transform.searchItems.errorInitializationTitle', { - defaultMessage: `An error occurred initializing the Kibana data view or saved search.`, - }) - ); - return; - } + if (isMounted.current) { + if (!isDataView(fetchedDataView) && fetchedSavedSearch === undefined) { + setError( + i18n.translate('xpack.transform.searchItems.errorInitializationTitle', { + defaultMessage: `An error occurred initializing the Kibana data view or saved search.`, + }) + ); + return; + } - setSearchItems(createSearchItems(fetchedDataView, fetchedSavedSearch, uiSettings)); - setError(undefined); + setSearchItems(createSearchItems(fetchedDataView, fetchedSavedSearch, uiSettings)); + setError(undefined); + } } useEffect(() => { diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.test.ts b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.test.ts similarity index 95% rename from x-pack/plugins/transform/public/app/hooks/use_pivot_data.test.ts rename to x-pack/plugins/transform/public/app/hooks/use_transform_config_data.test.ts index 6c354c1ed953e..1ee68bdcb48fa 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.test.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getCombinedProperties } from './use_pivot_data'; +import { getCombinedProperties } from './use_transform_config_data'; import { ES_FIELD_TYPES } from '@kbn/field-types'; describe('getCombinedProperties', () => { diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts similarity index 95% rename from x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts rename to x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts index 79976eb6d6355..1baef3e7fe8d3 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_transform_config_data.ts @@ -27,7 +27,7 @@ import { import { getErrorMessage } from '../../../common/utils/errors'; import { useAppDependencies } from '../app_dependencies'; -import { getPreviewTransformRequestBody, PivotQuery } from '../common'; +import { getPreviewTransformRequestBody, type TransformConfigQuery } from '../common'; import { SearchItems } from './use_search_items'; import { useApi } from './use_api'; @@ -95,12 +95,13 @@ export function getCombinedProperties( }; } -export const usePivotData = ( - dataViewTitle: SearchItems['dataView']['title'], - query: PivotQuery, +export const useTransformConfigData = ( + dataView: SearchItems['dataView'], + query: TransformConfigQuery, validationStatus: StepDefineExposedState['validationStatus'], requestPayload: StepDefineExposedState['previewRequest'], - combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'] + combinedRuntimeMappings?: StepDefineExposedState['runtimeMappings'], + timeRangeMs?: StepDefineExposedState['timeRangeMs'] ): UseIndexDataReturnType => { const [previewMappingsProperties, setPreviewMappingsProperties] = useState({}); @@ -166,10 +167,11 @@ export const usePivotData = ( setStatus(INDEX_STATUS.LOADING); const previewRequest = getPreviewTransformRequestBody( - dataViewTitle, + dataView, query, requestPayload, - combinedRuntimeMappings + combinedRuntimeMappings, + timeRangeMs ); const resp = await api.getTransformsPreview(previewRequest); @@ -238,7 +240,10 @@ export const usePivotData = ( getPreviewData(); // custom comparison /* eslint-disable react-hooks/exhaustive-deps */ - }, [dataViewTitle, JSON.stringify([requestPayload, query, combinedRuntimeMappings])]); + }, [ + dataView.getIndexPattern(), + JSON.stringify([requestPayload, query, combinedRuntimeMappings, timeRangeMs]), + ]); if (sortingColumns.length > 0) { const sortingColumnsWithTypes = sortingColumns.map((c) => ({ diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.tsx new file mode 100644 index 0000000000000..a57d83b75aa10 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/date_picker_apply_switch.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, { FC } from 'react'; + +import { EuiSwitch } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { StepDefineFormHook } from '../step_define'; + +export const DatePickerApplySwitch: FC = ({ + datePicker: { + actions: { setDatePickerApplyEnabled }, + state: { isDatePickerApplyEnabled }, + }, +}) => { + return ( + { + setDatePickerApplyEnabled(!isDatePickerApplyEnabled); + }} + data-test-subj="transformDatePickerApplySwitch" + /> + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/index.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/index.ts new file mode 100644 index 0000000000000..cc1760017caf8 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/date_picker_apply_switch/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 { DatePickerApplySwitch } from './date_picker_apply_switch'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts index c75da651f79d0..7e6d336d57a4b 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_default_step_define_state.ts @@ -21,6 +21,7 @@ export function getDefaultStepDefineState(searchItems: SearchItems): StepDefineE groupByList: {} as PivotGroupByConfigDict, isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, + isDatePickerApplyEnabled: false, searchLanguage: QUERY_LANGUAGE_KUERY, searchString: undefined, searchQuery: searchItems.savedSearch !== undefined ? searchItems.combinedQuery : defaultSearch, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts index c8dc63cae1f9a..2e23cc0e9047f 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/types.ts @@ -24,8 +24,8 @@ import { PivotConfigDefinition, } from '../../../../../../../common/types/transform'; import { LatestFunctionConfig } from '../../../../../../../common/api_schemas/transforms'; - import { RUNTIME_FIELD_TYPES } from '../../../../../../../common/shared_imports'; +import type { TimeRangeMs } from '../../../../../../../common/types/date_picker'; export interface ErrorMessage { query: string; @@ -62,13 +62,15 @@ export interface StepDefineExposedState { sourceConfigUpdated: boolean; valid: boolean; validationStatus: { isValid: boolean; errorMessage?: string }; + runtimeMappings?: RuntimeMappings; + runtimeMappingsUpdated: boolean; + isRuntimeMappingsEditorEnabled: boolean; + timeRangeMs?: TimeRangeMs; + isDatePickerApplyEnabled: boolean; /** * Undefined when the form is incomplete or invalid */ previewRequest: { latest: LatestFunctionConfig } | { pivot: PivotConfigDefinition } | undefined; - runtimeMappings?: RuntimeMappings; - runtimeMappingsUpdated: boolean; - isRuntimeMappingsEditorEnabled: boolean; } export function isPivotPartialRequest(arg: unknown): arg is { pivot: PivotConfigDefinition } { diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_date_picker.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_date_picker.ts new file mode 100644 index 0000000000000..64a825e424220 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_date_picker.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useEffect, useMemo, useState } from 'react'; +import { merge } from 'rxjs'; + +import type { TimeRange } from '@kbn/es-query'; +import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker'; + +import type { TimeRangeMs } from '../../../../../../../common/types/date_picker'; + +import { StepDefineExposedState } from '../common'; +import { StepDefineFormProps } from '../step_define_form'; + +export const useDatePicker = ( + defaults: StepDefineExposedState, + dataView: StepDefineFormProps['searchItems']['dataView'] +) => { + const hasValidTimeField = useMemo( + () => dataView.timeFieldName !== undefined && dataView.timeFieldName !== '', + [dataView.timeFieldName] + ); + + const timefilter = useTimefilter({ + timeRangeSelector: hasValidTimeField, + autoRefreshSelector: false, + }); + + // The internal state of the date picker apply button. + const [isDatePickerApplyEnabled, setDatePickerApplyEnabled] = useState( + defaults.isDatePickerApplyEnabled + ); + + // The time range selected via the date picker + const [timeRange, setTimeRange] = useState(); + + // Set up subscriptions to date picker updates + useEffect(() => { + const updateTimeRange = () => setTimeRange(timefilter.getTime()); + + const timefilterUpdateSubscription = merge( + timefilter.getAutoRefreshFetch$(), + timefilter.getTimeUpdate$(), + mlTimefilterRefresh$ + ).subscribe(updateTimeRange); + + const timefilterEnabledSubscription = timefilter + .getEnabledUpdated$() + .subscribe(updateTimeRange); + + return () => { + timefilterUpdateSubscription.unsubscribe(); + timefilterEnabledSubscription.unsubscribe(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Derive ms timestamps from timeRange updates. + const timeRangeMs: TimeRangeMs | undefined = useMemo(() => { + const timefilterActiveBounds = timefilter.getActiveBounds(); + if ( + timefilterActiveBounds !== undefined && + timefilterActiveBounds.min !== undefined && + timefilterActiveBounds.max !== undefined + ) { + return { + from: timefilterActiveBounds.min.valueOf(), + to: timefilterActiveBounds.max.valueOf(), + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [timeRange]); + + return { + actions: { setDatePickerApplyEnabled }, + state: { isDatePickerApplyEnabled, hasValidTimeField, timeRange, timeRangeMs }, + }; +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts index 4ea56b557c7ee..e8d56fc002981 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_search_bar.ts @@ -10,7 +10,7 @@ import { useState } from 'react'; import { toElasticsearchQuery, fromKueryExpression, luceneStringToDsl } from '@kbn/es-query'; import type { Query } from '@kbn/es-query'; -import { getPivotQuery } from '../../../../../common'; +import { getTransformConfigQuery } from '../../../../../common'; import { ErrorMessage, @@ -65,7 +65,7 @@ export const useSearchBar = ( } }; - const pivotQuery = getPivotQuery(searchQuery); + const transformConfigQuery = getTransformConfigQuery(searchQuery); return { actions: { @@ -79,7 +79,7 @@ export const useSearchBar = ( }, state: { errorMessage, - pivotQuery, + transformConfigQuery, searchInput, searchLanguage, searchQuery, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts index 1cd0a154707b3..849883e6c3041 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_step_define_form.ts @@ -15,6 +15,7 @@ import { StepDefineFormProps } from '../step_define_form'; import { useAdvancedPivotEditor } from './use_advanced_pivot_editor'; import { useAdvancedSourceEditor } from './use_advanced_source_editor'; +import { useDatePicker } from './use_date_picker'; import { usePivotConfig } from './use_pivot_config'; import { useSearchBar } from './use_search_bar'; import { useLatestFunctionConfig } from './use_latest_function_config'; @@ -29,6 +30,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi const [transformFunction, setTransformFunction] = useState(defaults.transformFunction); + const datePicker = useDatePicker(defaults, dataView); const searchBar = useSearchBar(defaults, dataView); const pivotConfig = usePivotConfig(defaults, dataView); @@ -39,8 +41,8 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi ); const previewRequest = getPreviewTransformRequestBody( - dataView.getIndexPattern(), - searchBar.state.pivotQuery, + dataView, + searchBar.state.transformConfigQuery, pivotConfig.state.requestPayload, defaults?.runtimeMappings ); @@ -58,8 +60,8 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi const runtimeMappings = runtimeMappingsEditor.state.runtimeMappings; if (!advancedSourceEditor.state.isAdvancedSourceEditorEnabled) { const previewRequestUpdate = getPreviewTransformRequestBody( - dataView.getIndexPattern(), - searchBar.state.pivotQuery, + dataView, + searchBar.state.transformConfigQuery, pivotConfig.state.requestPayload, runtimeMappings ); @@ -79,6 +81,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi groupByList: pivotConfig.state.groupByList, isAdvancedPivotEditorEnabled: advancedPivotEditor.state.isAdvancedPivotEditorEnabled, isAdvancedSourceEditorEnabled: advancedSourceEditor.state.isAdvancedSourceEditorEnabled, + isDatePickerApplyEnabled: datePicker.state.isDatePickerApplyEnabled, searchLanguage: searchBar.state.searchLanguage, searchString: searchBar.state.searchString, searchQuery: searchBar.state.searchQuery, @@ -98,12 +101,14 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi runtimeMappings, runtimeMappingsUpdated: runtimeMappingsEditor.state.runtimeMappingsUpdated, isRuntimeMappingsEditorEnabled: runtimeMappingsEditor.state.isRuntimeMappingsEditorEnabled, + timeRangeMs: datePicker.state.timeRangeMs, }); // custom comparison /* eslint-disable react-hooks/exhaustive-deps */ }, [ JSON.stringify(advancedPivotEditor.state), JSON.stringify(advancedSourceEditor.state), + JSON.stringify(datePicker.state), pivotConfig.state, JSON.stringify(searchBar.state), JSON.stringify([ @@ -121,6 +126,7 @@ export const useStepDefineForm = ({ overrides, onChange, searchItems }: StepDefi advancedPivotEditor, advancedSourceEditor, runtimeMappingsEditor, + datePicker, pivotConfig, latestFunctionConfig, searchBar, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx new file mode 100644 index 0000000000000..efa28de596a18 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_function_form.tsx @@ -0,0 +1,120 @@ +/* + * 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 { + EuiButton, + EuiButtonIcon, + EuiCopy, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; + +import { AdvancedPivotEditor } from '../advanced_pivot_editor'; +import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch'; +import { PivotConfiguration } from '../pivot_configuration'; + +import type { StepDefineFormHook } from './hooks/use_step_define_form'; + +const advancedEditorsSidebarWidth = '220px'; + +interface PivotFunctionFormProps { + applyPivotChangesHandler: () => void; + copyToClipboardPivot: string; + copyToClipboardPivotDescription: string; + stepDefineForm: StepDefineFormHook; +} + +export const PivotFunctionForm: FC = ({ + applyPivotChangesHandler, + copyToClipboardPivot, + copyToClipboardPivotDescription, + stepDefineForm, +}) => { + const { esTransformPivot } = useDocumentationLinks(); + + const { isAdvancedPivotEditorEnabled, isAdvancedPivotEditorApplyButtonEnabled } = + stepDefineForm.advancedPivotEditor.state; + + return ( + + {/* Flex Column #1: Pivot Config Form / Advanced Pivot Config Editor */} + + {!isAdvancedPivotEditorEnabled && } + {isAdvancedPivotEditorEnabled && ( + + )} + + + + + + + + + + + + {(copy: () => void) => ( + + )} + + + + + + {isAdvancedPivotEditorEnabled && ( + + + + <> + {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', { + defaultMessage: + 'The advanced editor allows you to edit the pivot configuration of the transform.', + })}{' '} + + {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpTextLink', { + defaultMessage: 'Learn more about available options.', + })} + + + + + + {i18n.translate('xpack.transform.stepDefineForm.advancedEditorApplyButtonText', { + defaultMessage: 'Apply changes', + })} + + + )} + + + + ); +}; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx index 682d1bde11c32..beb4020378409 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.test.tsx @@ -9,12 +9,11 @@ import React from 'react'; import { render, waitFor } from '@testing-library/react'; import { I18nProvider } from '@kbn/i18n-react'; - +import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; - import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -const startMock = coreMock.createStart(); +import { timefilterServiceMock } from '@kbn/data-plugin/public/query/timefilter/timefilter_service.mock'; import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs'; @@ -28,11 +27,24 @@ import { SearchItems } from '../../../../hooks/use_search_items'; import { getAggNameConflictToastMessages } from './common'; import { StepDefineForm } from './step_define_form'; +import { MlSharedContext } from '../../../../__mocks__/shared_context'; +import { getMlSharedImports } from '../../../../../shared_imports'; + jest.mock('../../../../../shared_imports'); jest.mock('../../../../app_dependencies'); -import { MlSharedContext } from '../../../../__mocks__/shared_context'; -import { getMlSharedImports } from '../../../../../shared_imports'; +const startMock = coreMock.createStart(); + +const getMockedDatePickerDependencies = () => { + return { + data: { + query: { + timefilter: timefilterServiceMock.createStartContract(), + }, + }, + notifications: {}, + } as unknown as DatePickerDependencies; +}; const createMockWebStorage = () => ({ clear: jest.fn(), @@ -75,7 +87,9 @@ describe('Transform: ', () => { - + + + diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index f86d693e605e2..c615e553b8984 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -5,9 +5,8 @@ * 2.0. */ -import React, { useMemo, FC } from 'react'; - -import { i18n } from '@kbn/i18n'; +import React, { useEffect, useMemo, FC } from 'react'; +import { merge } from 'rxjs'; import { EuiButton, @@ -17,20 +16,25 @@ import { EuiFlexItem, EuiForm, EuiFormRow, - EuiHorizontalRule, + EuiIconTip, EuiLink, EuiSpacer, EuiText, + EuiTitle, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { mlTimefilterRefresh$, useTimefilter, DatePickerWrapper } from '@kbn/ml-date-picker'; +import { useUrlState } from '@kbn/ml-url-state'; + import { PivotAggDict } from '../../../../../../common/types/pivot_aggs'; import { PivotGroupByDict } from '../../../../../../common/types/pivot_group_by'; +import { TRANSFORM_FUNCTION } from '../../../../../../common/constants'; import { getIndexDevConsoleStatement, - getPivotPreviewDevConsoleStatement, + getTransformPreviewDevConsoleStatement, } from '../../../../common/data_grid'; - import { getPreviewTransformRequestBody, PivotAggsConfigDict, @@ -40,24 +44,36 @@ import { } from '../../../../common'; import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; import { useIndexData } from '../../../../hooks/use_index_data'; -import { usePivotData } from '../../../../hooks/use_pivot_data'; +import { useTransformConfigData } from '../../../../hooks/use_transform_config_data'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { SearchItems } from '../../../../hooks/use_search_items'; +import { getAggConfigFromEsAgg } from '../../../../common/pivot_aggs'; -import { AdvancedPivotEditor } from '../advanced_pivot_editor'; -import { AdvancedPivotEditorSwitch } from '../advanced_pivot_editor_switch'; import { AdvancedQueryEditorSwitch } from '../advanced_query_editor_switch'; import { AdvancedSourceEditor } from '../advanced_source_editor'; -import { PivotConfiguration } from '../pivot_configuration'; +import { DatePickerApplySwitch } from '../date_picker_apply_switch'; import { SourceSearchBar } from '../source_search_bar'; +import { AdvancedRuntimeMappingsSettings } from '../advanced_runtime_mappings_settings'; import { StepDefineExposedState } from './common'; import { useStepDefineForm } from './hooks/use_step_define_form'; -import { getAggConfigFromEsAgg } from '../../../../common/pivot_aggs'; import { TransformFunctionSelector } from './transform_function_selector'; -import { TRANSFORM_FUNCTION } from '../../../../../../common/constants'; import { LatestFunctionForm } from './latest_function_form'; -import { AdvancedRuntimeMappingsSettings } from '../advanced_runtime_mappings_settings'; +import { PivotFunctionForm } from './pivot_function_form'; + +const ALLOW_TIME_RANGE_ON_TRANSFORM_CONFIG = false; + +const advancedEditorsSidebarWidth = '220px'; + +export const ConfigSectionTitle: FC<{ title: string }> = ({ title }) => ( + <> + + + {title} + + + +); export interface StepDefineFormProps { overrides?: StepDefineExposedState; @@ -66,6 +82,7 @@ export interface StepDefineFormProps { } export const StepDefineForm: FC = React.memo((props) => { + const [globalState, setGlobalState] = useUrlState('_g'); const { searchItems } = props; const { dataView } = searchItems; const indexPattern = useMemo(() => dataView.getIndexPattern(), [dataView]); @@ -75,24 +92,18 @@ export const StepDefineForm: FC = React.memo((props) => { const toastNotifications = useToastNotifications(); const stepDefineForm = useStepDefineForm(props); - const { - advancedEditorConfig, - isAdvancedPivotEditorEnabled, - isAdvancedPivotEditorApplyButtonEnabled, - } = stepDefineForm.advancedPivotEditor.state; + const { advancedEditorConfig } = stepDefineForm.advancedPivotEditor.state; const { advancedEditorSourceConfig, isAdvancedSourceEditorEnabled, isAdvancedSourceEditorApplyButtonEnabled, } = stepDefineForm.advancedSourceEditor.state; - const pivotQuery = stepDefineForm.searchBar.state.pivotQuery; + const { isDatePickerApplyEnabled, timeRangeMs } = stepDefineForm.datePicker.state; + const { transformConfigQuery } = stepDefineForm.searchBar.state; + const { runtimeMappings } = stepDefineForm.runtimeMappingsEditor.state; const indexPreviewProps = { - ...useIndexData( - dataView, - stepDefineForm.searchBar.state.pivotQuery, - stepDefineForm.runtimeMappingsEditor.state.runtimeMappings - ), + ...useIndexData(dataView, transformConfigQuery, runtimeMappings, timeRangeMs), dataTestSubj: 'transformIndexPreview', toastNotifications, }; @@ -101,16 +112,7 @@ export const StepDefineForm: FC = React.memo((props) => { ? stepDefineForm.pivotConfig.state : stepDefineForm.latestFunctionConfig; - const previewRequest = getPreviewTransformRequestBody( - indexPattern, - pivotQuery, - stepDefineForm.transformFunction === TRANSFORM_FUNCTION.PIVOT - ? stepDefineForm.pivotConfig.state.requestPayload - : stepDefineForm.latestFunctionConfig.requestPayload, - stepDefineForm.runtimeMappingsEditor.state.runtimeMappings - ); - - const copyToClipboardSource = getIndexDevConsoleStatement(pivotQuery, indexPattern); + const copyToClipboardSource = getIndexDevConsoleStatement(transformConfigQuery, indexPattern); const copyToClipboardSourceDescription = i18n.translate( 'xpack.transform.indexPreview.copyClipboardTooltip', { @@ -118,7 +120,17 @@ export const StepDefineForm: FC = React.memo((props) => { } ); - const copyToClipboardPivot = getPivotPreviewDevConsoleStatement(previewRequest); + const copyToClipboardPreviewRequest = getPreviewTransformRequestBody( + dataView, + transformConfigQuery, + requestPayload, + runtimeMappings, + isDatePickerApplyEnabled ? timeRangeMs : undefined + ); + + const copyToClipboardPivot = getTransformPreviewDevConsoleStatement( + copyToClipboardPreviewRequest + ); const copyToClipboardPivotDescription = i18n.translate( 'xpack.transform.pivotPreview.copyClipboardTooltip', { @@ -126,18 +138,16 @@ export const StepDefineForm: FC = React.memo((props) => { } ); - const pivotPreviewProps = { - ...usePivotData( - indexPattern, - pivotQuery, + const previewProps = { + ...useTransformConfigData( + dataView, + transformConfigQuery, validationStatus, requestPayload, - stepDefineForm.runtimeMappingsEditor.state.runtimeMappings + runtimeMappings, + timeRangeMs ), dataTestSubj: 'transformPivotPreview', - title: i18n.translate('xpack.transform.pivotPreview.transformPreviewTitle', { - defaultMessage: 'Transform preview', - }), toastNotifications, ...(stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST ? { @@ -192,9 +202,52 @@ export const StepDefineForm: FC = React.memo((props) => { stepDefineForm.advancedPivotEditor.actions.setAdvancedPivotEditorApplyButtonEnabled(false); }; - const { esQueryDsl, esTransformPivot } = useDocumentationLinks(); + const { esQueryDsl } = useDocumentationLinks(); + + const hasValidTimeField = useMemo( + () => dataView.timeFieldName !== undefined && dataView.timeFieldName !== '', + [dataView.timeFieldName] + ); + + const timefilter = useTimefilter({ + timeRangeSelector: dataView?.timeFieldName !== undefined, + autoRefreshSelector: false, + }); + + useEffect(() => { + if (globalState?.time !== undefined) { + timefilter.setTime({ + from: globalState.time.from, + to: globalState.time.to, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(globalState?.time), timefilter]); + + useEffect(() => { + if (globalState?.refreshInterval !== undefined) { + timefilter.setRefreshInterval(globalState.refreshInterval); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(globalState?.refreshInterval), timefilter]); - const advancedEditorsSidebarWidth = '220px'; + useEffect(() => { + const timeUpdateSubscription = merge( + timefilter.getAutoRefreshFetch$(), + timefilter.getTimeUpdate$(), + mlTimefilterRefresh$ + ).subscribe(() => { + if (setGlobalState) { + setGlobalState({ + time: timefilter.getTime(), + refreshInterval: timefilter.getRefreshInterval(), + }); + } + }); + return () => { + timeUpdateSubscription.unsubscribe(); + }; + }); return (
@@ -206,6 +259,8 @@ export const StepDefineForm: FC = React.memo((props) => { /> + + {searchItems.savedSearch === undefined && ( = React.memo((props) => { )} + {hasValidTimeField && ( + + {i18n.translate('xpack.transform.stepDefineForm.datePickerLabel', { + defaultMessage: 'Time range', + })}{' '} + + + } + > + + {/* Flex Column #1: Date Picker */} + + + + {/* Flex Column #2: Apply-To-Config option */} + + {ALLOW_TIME_RANGE_ON_TRANSFORM_CONFIG && ( + + + {searchItems.savedSearch === undefined && ( + + )} + + + )} + + + + )} + <> @@ -314,87 +415,30 @@ export const StepDefineForm: FC = React.memo((props) => { - + + + - + + + {stepDefineForm.transformFunction === TRANSFORM_FUNCTION.PIVOT ? ( - - {/* Flex Column #1: Pivot Config Form / Advanced Pivot Config Editor */} - - {!isAdvancedPivotEditorEnabled && ( - - )} - {isAdvancedPivotEditorEnabled && ( - - )} - - - - - - - - - - - - {(copy: () => void) => ( - - )} - - - - - - {isAdvancedPivotEditorEnabled && ( - - - - <> - {i18n.translate('xpack.transform.stepDefineForm.advancedEditorHelpText', { - defaultMessage: - 'The advanced editor allows you to edit the pivot configuration of the transform.', - })}{' '} - - {i18n.translate( - 'xpack.transform.stepDefineForm.advancedEditorHelpTextLink', - { - defaultMessage: 'Learn more about available options.', - } - )} - - - - - - {i18n.translate( - 'xpack.transform.stepDefineForm.advancedEditorApplyButtonText', - { - defaultMessage: 'Apply changes', - } - )} - - - )} - - - + ) : null} {stepDefineForm.transformFunction === TRANSFORM_FUNCTION.LATEST ? ( = React.memo((props) => { {(stepDefineForm.transformFunction !== TRANSFORM_FUNCTION.LATEST || stepDefineForm.latestFunctionConfig.sortFieldOptions.length > 0) && ( - <> - - - + + <> + + + + )}
); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx index 140d58523b38a..630e6278dceba 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_summary.tsx @@ -14,13 +14,13 @@ import { EuiBadge, EuiCodeBlock, EuiForm, EuiFormRow, EuiSpacer, EuiText } from import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { - getPivotQuery, - getPivotPreviewDevConsoleStatement, + getTransformConfigQuery, + getTransformPreviewDevConsoleStatement, getPreviewTransformRequestBody, isDefaultQuery, isMatchAllQuery, } from '../../../../common'; -import { usePivotData } from '../../../../hooks/use_pivot_data'; +import { useTransformConfigData } from '../../../../hooks/use_transform_config_data'; import { SearchItems } from '../../../../hooks/use_search_items'; import { AggListSummary } from '../aggregation_list'; @@ -37,6 +37,8 @@ interface Props { export const StepDefineSummary: FC = ({ formState: { + isDatePickerApplyEnabled, + timeRangeMs, runtimeMappings, searchString, searchQuery, @@ -49,31 +51,33 @@ export const StepDefineSummary: FC = ({ searchItems, }) => { const { - ml: { DataGrid }, + ml: { formatHumanReadableDateTimeSeconds, DataGrid }, } = useAppDependencies(); const toastNotifications = useToastNotifications(); - const pivotQuery = getPivotQuery(searchQuery); + const transformConfigQuery = getTransformConfigQuery(searchQuery); const previewRequest = getPreviewTransformRequestBody( - searchItems.dataView.getIndexPattern(), - pivotQuery, + searchItems.dataView, + transformConfigQuery, partialPreviewRequest, - runtimeMappings + runtimeMappings, + isDatePickerApplyEnabled ? timeRangeMs : undefined ); - const pivotPreviewProps = usePivotData( - searchItems.dataView.getIndexPattern(), - pivotQuery, + const pivotPreviewProps = useTransformConfigData( + searchItems.dataView, + transformConfigQuery, validationStatus, partialPreviewRequest, - runtimeMappings + runtimeMappings, + isDatePickerApplyEnabled ? timeRangeMs : undefined ); const isModifiedQuery = typeof searchString === 'undefined' && - !isDefaultQuery(pivotQuery) && - !isMatchAllQuery(pivotQuery); + !isDefaultQuery(transformConfigQuery) && + !isMatchAllQuery(transformConfigQuery); let uniqueKeys: string[] = []; let sortField = ''; @@ -94,6 +98,18 @@ export const StepDefineSummary: FC = ({ > {searchItems.dataView.getIndexPattern()} + {isDatePickerApplyEnabled && timeRangeMs && ( + + + {formatHumanReadableDateTimeSeconds(timeRangeMs.from)} -{' '} + {formatHumanReadableDateTimeSeconds(timeRangeMs.to)} + + + )} {typeof searchString === 'string' && ( = ({ overflowHeight={300} isCopyable > - {JSON.stringify(pivotQuery, null, 2)} + {JSON.stringify(transformConfigQuery, null, 2)} )} @@ -187,7 +203,7 @@ export const StepDefineSummary: FC = ({ = React.memo( // use an IIFE to avoid returning a Promise to useEffect. (async function () { const { searchQuery, previewRequest: partialPreviewRequest } = stepDefineState; - const pivotQuery = getPivotQuery(searchQuery); + const transformConfigQuery = getTransformConfigQuery(searchQuery); const previewRequest = getPreviewTransformRequestBody( - searchItems.dataView.getIndexPattern(), - pivotQuery, + searchItems.dataView, + transformConfigQuery, partialPreviewRequest, stepDefineState.runtimeMappings ); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/storage.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/storage.ts new file mode 100644 index 0000000000000..0bd126708fde8 --- /dev/null +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/storage.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { type FrozenTierPreference } from '@kbn/ml-date-picker'; + +export const TRANSFORM_FROZEN_TIER_PREFERENCE = 'transform.frozenDataTierPreference'; + +export type Transform = Partial<{ + [TRANSFORM_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; +}> | null; + +export type TransformKey = keyof Exclude; + +export type TransformStorageMapped = + T extends typeof TRANSFORM_FROZEN_TIER_PREFERENCE ? FrozenTierPreference | undefined : null; + +export const TRANSFORM_STORAGE_KEYS = [TRANSFORM_FROZEN_TIER_PREFERENCE] as const; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx index 0a221cf735395..f0ad9227ac672 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/wizard/wizard.tsx @@ -6,16 +6,24 @@ */ import React, { type FC, useRef, useState, createContext, useMemo } from 'react'; - -import { i18n } from '@kbn/i18n'; +import { pick } from 'lodash'; import { EuiSteps, EuiStepStatus } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { DataView } from '@kbn/data-views-plugin/public'; +import { DatePickerContextProvider } from '@kbn/ml-date-picker'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; +import { StorageContextProvider } from '@kbn/ml-local-storage'; +import { UrlStateProvider } from '@kbn/ml-url-state'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; + import type { TransformConfigUnion } from '../../../../../../common/types/transform'; import { getCreateTransformRequestBody } from '../../../../common'; import { SearchItems } from '../../../../hooks/use_search_items'; +import { useAppDependencies } from '../../../../app_dependencies'; import { applyTransformConfigToDefineState, @@ -34,6 +42,10 @@ import { import { WizardNav } from '../wizard_nav'; import type { RuntimeMappings } from '../step_define/common/types'; +import { TRANSFORM_STORAGE_KEYS } from './storage'; + +const localStorage = new Storage(window.localStorage); + enum WIZARD_STEPS { DEFINE, DETAILS, @@ -94,6 +106,7 @@ export const CreateTransformWizardContext = createContext<{ }); export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) => { + const appDependencies = useAppDependencies(); const { dataView } = searchItems; // The current WIZARD_STEP @@ -113,7 +126,7 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) const [stepCreateState, setStepCreateState] = useState(getDefaultStepCreateState); const transformConfig = getCreateTransformRequestBody( - dataView.getIndexPattern(), + dataView, stepDefineState, stepDetailsState ); @@ -206,11 +219,24 @@ export const Wizard: FC = React.memo(({ cloneConfig, searchItems }) const stepsConfig = [stepDefine, stepDetails, stepCreate]; + const datePickerDeps = { + ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings']), + toMountPoint, + wrapWithTheme, + uiSettingsKeys: UI_SETTINGS, + }; + return ( - + + + + + + + ); }); diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx index 51dfc449b89b2..bfc5d4f664b15 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx @@ -7,11 +7,13 @@ import React, { useMemo, FC } from 'react'; +import type { DataView } from '@kbn/data-views-plugin/public'; + import { TransformConfigUnion } from '../../../../../../common/types/transform'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; -import { getPivotQuery } from '../../../../common'; -import { usePivotData } from '../../../../hooks/use_pivot_data'; +import { getTransformConfigQuery } from '../../../../common'; +import { useTransformConfigData } from '../../../../hooks/use_transform_config_data'; import { SearchItems } from '../../../../hooks/use_search_items'; import { @@ -38,15 +40,15 @@ export const ExpandedRowPreviewPane: FC = ({ transf [transformConfig] ); - const pivotQuery = useMemo(() => getPivotQuery(searchQuery), [searchQuery]); + const transformConfigQuery = useMemo(() => getTransformConfigQuery(searchQuery), [searchQuery]); const dataViewTitle = Array.isArray(transformConfig.source.index) ? transformConfig.source.index.join(',') : transformConfig.source.index; - const pivotPreviewProps = usePivotData( - dataViewTitle, - pivotQuery, + const pivotPreviewProps = useTransformConfigData( + { getIndexPattern: () => dataViewTitle } as DataView, + transformConfigQuery, validationStatus, previewRequest, runtimeMappings diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index 8cb77da845d58..ca4191088a8b1 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -46,6 +46,10 @@ "@kbn/field-types", "@kbn/ml-nested-property", "@kbn/ml-is-defined", + "@kbn/ml-date-picker", + "@kbn/ml-url-state", + "@kbn/ml-local-storage", + "@kbn/ml-query-utils", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 0737305c309ec..4e4e22cefafec 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -2079,7 +2079,6 @@ "discover.gridSampleSize.description": "Vous voyez les {sampleSize} premiers échantillons de documents qui correspondent à votre recherche. Pour modifier cette valeur, accédez à {advancedSettingsLink}.", "discover.howToSeeOtherMatchingDocumentsDescription": "Voici les {sampleSize} premiers documents correspondant à votre recherche. Veuillez affiner celle-ci pour en voir plus.", "discover.noMatchRoute.bannerText": "L'application Discover ne reconnaît pas cet itinéraire : {route}", - "discover.noResults.tryRemovingOrDisablingFilters": "Essayez de supprimer ou de {disablingFiltersLink}.", "discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}", "discover.savedSearchAliasMatchRedirect.objectNoun": "Recherche {savedSearch}", "discover.savedSearchURLConflictCallout.objectNoun": "Recherche {savedSearch}", @@ -2321,15 +2320,9 @@ "discover.localMenu.shareSearchDescription": "Partager la recherche", "discover.localMenu.shareTitle": "Partager", "discover.noMatchRoute.bannerTitleText": "Page introuvable", - "discover.noResults.adjustFilters": "Modifiez les filtres.", - "discover.noResults.adjustSearch": "Modifiez la requête.", - "discover.noResults.expandYourTimeRangeTitle": "Étendre la plage temporelle", "discover.noResults.noDocumentsOrCheckPermissionsDescription": "Assurez-vous de disposer de l'autorisation d'afficher les index et vérifiez qu'ils contiennent des documents.", - "discover.noResults.queryMayNotMatchTitle": "Essayez de rechercher sur une période plus longue.", "discover.noResults.searchExamples.noResultsBecauseOfError": "Une erreur s’est produite lors de la récupération des résultats de recherche.", "discover.noResults.searchExamples.noResultsMatchSearchCriteriaTitle": "Aucun résultat ne correspond à vos critères de recherche.", - "discover.noResults.temporaryDisablingFiltersLinkText": "désactiver temporairement les filtres", - "discover.noResults.trySearchingForDifferentCombination": "Essayez de rechercher une autre combinaison de termes.", "discover.noResultsFound": "Résultat introuvable", "discover.notifications.invalidTimeRangeText": "La plage temporelle spécifiée n'est pas valide (de : \"{from}\" à \"{to}\").", "discover.notifications.invalidTimeRangeTitle": "Plage temporelle non valide", @@ -3015,15 +3008,6 @@ "guidedOnboarding.quitGuideModal.modalDescription": "Vous pouvez redémarrer le guide de configuration à tout moment à partir du menu Aide.", "guidedOnboarding.quitGuideModal.modalTitle": "Quitter ce guide ?", "guidedOnboarding.quitGuideModal.quitButtonLabel": "Quitter le guide", - "guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress} étapes", - "guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "Continuer", - "guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "Terminé", - "guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "En cours", - "guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "Créez une expérience de recherche pour vos sites web, vos applications, votre contenu sur le lieu de travail, etc.", - "guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "Rechercher dans mes données", - "guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "Afficher le guide", - "guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "rechercher", - "guidedOnboardingPackage.gettingStarted.search.iconName": "Logo Entreprise Search", "home.loadTutorials.requestFailedErrorMessage": "Échec de la requête avec le code de statut : {status}", "home.tutorial.addDataToKibanaDescription": "En plus d'ajouter {integrationsLink}, vous pouvez essayer l'exemple de données ou charger vos propres données.", "home.tutorial.noTutorialLabel": "Tutoriel {tutorialId} introuvable", @@ -10071,7 +10055,6 @@ "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "Affichage de {pageStart}-{pageEnd} sur {total} {type}", "xpack.csp.findings.findingsTableCell.addFilterButton": "Ajouter un filtre {field}", "xpack.csp.findings.findingsTableCell.addNegateFilterButton": "Ajouter un filtre {field} négatif", - "xpack.csp.findings.resourceFindings.resourceFindingsPageTitle": "{resourceName} - Résultats", "xpack.csp.rules.header.rulesCountLabel": "{count, plural, one { règle} other { règles}}", "xpack.csp.rules.header.totalRulesCount": "Affichage des {rules}", "xpack.csp.rules.rulePageHeader.pageHeaderTitle": "Règles - {integrationName}", @@ -13253,7 +13236,6 @@ "xpack.fileUpload.geoUploadWizard.creatingDataView": "Création de la vue de données : {indexName}", "xpack.fileUpload.geoUploadWizard.dataIndexingStarted": "Création de l'index : {indexName}", "xpack.fileUpload.geoUploadWizard.writingToIndex": "Écriture dans l'index : {progress} % terminé", - "xpack.fileUpload.importComplete.failedFeaturesMsg": "Impossible d'indexer {numFailures} fonctionnalités.", "xpack.fileUpload.importComplete.permissionFailureMsg": "Vous ne disposez pas d'autorisation pour créer ni importer des données dans l'index \"{indexName}\".", "xpack.fileUpload.importComplete.uploadFailureMsgErrorBlock": "Erreur : {reason}", "xpack.fileUpload.importComplete.uploadSuccessMsg": "{numFeatures} fonctionnalités indexées.", @@ -13298,7 +13280,7 @@ "xpack.fleet.addIntegration.installAgentStepTitle": "Ces étapes configurent et enregistrent l'agent Elastic Agent dans Fleet afin d'en centraliser la gestion tout en déployant automatiquement les mises à jour. Comme alternative à Fleet, les utilisateurs avancés peuvent exécuter des agents dans {standaloneLink}.", "xpack.fleet.addIntegration.standaloneWarning": "La configuration des intégrations en exécutant Elastic Agent en mode autonome est une opération avancée. Si possible, nous vous conseillons d'utiliser plutôt {link}. ", "xpack.fleet.agentActivity.completedTitle": "{nbAgents} {agents} {completedText}{offlineText}", - "xpack.fleet.agentActivity.inProgressTitle": "{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}", + "xpack.fleet.agentActivity.inProgressTitle": "{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}{failuresText}", "xpack.fleet.agentActivityFlyout.cancelledDescription": "Annulé le {date}", "xpack.fleet.agentActivityFlyout.cancelledTitle": "Agent {cancelledText} annulé", "xpack.fleet.agentActivityFlyout.completedDescription": "Terminé {date}", @@ -29729,7 +29711,6 @@ "xpack.securitySolution.expandedValue.links.viewUserSummary": "Afficher le résumé de l'utilisateur", "xpack.securitySolution.expandedValue.showTopN.showTopValues": "Afficher les valeurs les plus élevées", "xpack.securitySolution.featureCatalogueDescription": "Prévenez, collectez, détectez et traitez les menaces pour une protection unifiée dans toute votre infrastructure.", - "xpack.securitySolution.featureRegistr.subFeatures.fileOperations": "Opérations de fichier", "xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "Supprimer les cas et les commentaires", "xpack.securitySolution.featureRegistry.deleteSubFeatureName": "Supprimer", "xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "Cas", @@ -34070,7 +34051,6 @@ "xpack.synthetics.sourceConfiguration.heartbeatIndicesTitle": "Index Uptime", "xpack.synthetics.sourceConfiguration.indicesSectionTitle": "Index", "xpack.synthetics.sourceConfiguration.warningStateLabel": "Limite d'âge", - "xpack.synthetics.ssl": "SSL", "xpack.synthetics.stackManagement": "Gestion de la Suite", "xpack.synthetics.stepDetails.expected": "Attendus", "xpack.synthetics.stepDetails.objectCount": "Décompte de l'objet", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9439e3e65c59a..2b089e8c055b8 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2077,7 +2077,6 @@ "discover.gridSampleSize.description": "検索と一致する最初の{sampleSize}ドキュメントを表示しています。この値を変更するには、{advancedSettingsLink}に移動してください。", "discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの {sampleSize} 件のドキュメントです。他の結果を表示するには検索条件を絞ってください。", "discover.noMatchRoute.bannerText": "Discoverアプリケーションはこのルート{route}を認識できません", - "discover.noResults.tryRemovingOrDisablingFilters": "削除または{disablingFiltersLink}してください。", "discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}", "discover.savedSearchAliasMatchRedirect.objectNoun": "{savedSearch}検索", "discover.savedSearchURLConflictCallout.objectNoun": "{savedSearch}検索", @@ -2319,15 +2318,9 @@ "discover.localMenu.shareSearchDescription": "検索を共有します", "discover.localMenu.shareTitle": "共有", "discover.noMatchRoute.bannerTitleText": "ページが見つかりません", - "discover.noResults.adjustFilters": "フィルターを調整", - "discover.noResults.adjustSearch": "クエリを調整", - "discover.noResults.expandYourTimeRangeTitle": "時間範囲を拡大", "discover.noResults.noDocumentsOrCheckPermissionsDescription": "インデックスと含まれるドキュメントを表示する権限がありません。", - "discover.noResults.queryMayNotMatchTitle": "期間を長くして検索を試してください。", "discover.noResults.searchExamples.noResultsBecauseOfError": "検索結果の取得中にエラーが発生しました", "discover.noResults.searchExamples.noResultsMatchSearchCriteriaTitle": "検索条件と一致する結果がありません。", - "discover.noResults.temporaryDisablingFiltersLinkText": "フィルターを一時的に無効にしています", - "discover.noResults.trySearchingForDifferentCombination": "別の用語の組み合わせを検索してください。", "discover.noResultsFound": "結果が見つかりませんでした", "discover.notifications.invalidTimeRangeText": "指定された時間範囲が無効です。(開始:'{from}'、終了:'{to}')", "discover.notifications.invalidTimeRangeTitle": "無効な時間範囲", @@ -3013,15 +3006,6 @@ "guidedOnboarding.quitGuideModal.modalDescription": "[ヘルプ]メニューを使用すると、いつでもセットアップガイドを再開できます。", "guidedOnboarding.quitGuideModal.modalTitle": "このガイドを終了しますか?", "guidedOnboarding.quitGuideModal.quitButtonLabel": "ガイドを終了", - "guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress}ステップ", - "guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "続行", - "guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "完了", - "guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "進行中", - "guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "Webサイト、アプリケーション、workplaceコンテンツなどに合った、検索エクスペリエンスを作成します。", - "guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "データを検索", - "guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "ガイドを表示", - "guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "検索", - "guidedOnboardingPackage.gettingStarted.search.iconName": "エンタープライズ サーチロゴ", "home.loadTutorials.requestFailedErrorMessage": "リクエスト失敗、ステータスコード:{status}", "home.tutorial.addDataToKibanaDescription": "{integrationsLink}を追加するほかに、サンプルデータを試したり、独自のデータをアップロードしたりできます。", "home.tutorial.noTutorialLabel": "チュートリアル {tutorialId} が見つかりません", @@ -10060,7 +10044,6 @@ "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "{total}件中{pageStart}-{pageEnd}件の{type}を表示しています", "xpack.csp.findings.findingsTableCell.addFilterButton": "{field}フィルターを追加", "xpack.csp.findings.findingsTableCell.addNegateFilterButton": "{field}否定フィルターを追加", - "xpack.csp.findings.resourceFindings.resourceFindingsPageTitle": "{resourceName} - 調査結果", "xpack.csp.rules.header.rulesCountLabel": "{count, plural, other {個のルール}}", "xpack.csp.rules.header.totalRulesCount": "{rules}を表示しています", "xpack.csp.rules.rulePageHeader.pageHeaderTitle": "ルール - {integrationName}", @@ -13240,7 +13223,6 @@ "xpack.fileUpload.geoUploadWizard.creatingDataView": "データビュー{indexName}を作成しています", "xpack.fileUpload.geoUploadWizard.dataIndexingStarted": "インデックスを作成中:{indexName}", "xpack.fileUpload.geoUploadWizard.writingToIndex": "インデックスに書き込み中:{progress}%完了", - "xpack.fileUpload.importComplete.failedFeaturesMsg": "{numFailures}個の特長量にインデックスを作成できませんでした。", "xpack.fileUpload.importComplete.permissionFailureMsg": "インデックス\"{indexName}\"にデータを作成またはインポートするアクセス権がありません。", "xpack.fileUpload.importComplete.uploadFailureMsgErrorBlock": "エラー:{reason}", "xpack.fileUpload.importComplete.uploadSuccessMsg": "{numFeatures}個の特徴量にインデックスを作成しました。", @@ -13285,7 +13267,7 @@ "xpack.fleet.addIntegration.installAgentStepTitle": "FleetでElasticエージェントを構成および登録して、自動的に更新をデプロイしたり、一元的にエージェントを管理したりします。上級者ユーザーは、Fleetの代わりに、{standaloneLink}でエージェントを実行できます。", "xpack.fleet.addIntegration.standaloneWarning": "スタンドアロンモードでElasticエージェントを実行して統合を設定する方法は、上級者向けです。可能なかぎり、{link}を使用することをお勧めします。", "xpack.fleet.agentActivity.completedTitle": "{nbAgents} {agents} {completedText}{offlineText}", - "xpack.fleet.agentActivity.inProgressTitle": "{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}", + "xpack.fleet.agentActivity.inProgressTitle": "{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}{failuresText}", "xpack.fleet.agentActivityFlyout.cancelledDescription": "{date}にキャンセルされました", "xpack.fleet.agentActivityFlyout.cancelledTitle": "エージェント{cancelledText}がキャンセルされました", "xpack.fleet.agentActivityFlyout.completedDescription": "{date}に完了しました", @@ -29698,7 +29680,6 @@ "xpack.securitySolution.expandedValue.links.viewUserSummary": "ユーザー概要を表示", "xpack.securitySolution.expandedValue.showTopN.showTopValues": "上位の値を表示", "xpack.securitySolution.featureCatalogueDescription": "インフラストラクチャ全体の統合保護のため、脅威を防止、収集、検出し、それに対応します。", - "xpack.securitySolution.featureRegistr.subFeatures.fileOperations": "ファイル操作", "xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "ケースとコメントを削除", "xpack.securitySolution.featureRegistry.deleteSubFeatureName": "削除", "xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "ケース", @@ -34041,7 +34022,6 @@ "xpack.synthetics.sourceConfiguration.heartbeatIndicesTitle": "アップタイムインデックス", "xpack.synthetics.sourceConfiguration.indicesSectionTitle": "インデックス", "xpack.synthetics.sourceConfiguration.warningStateLabel": "使用期間上限", - "xpack.synthetics.ssl": "SSL", "xpack.synthetics.stackManagement": "スタック管理", "xpack.synthetics.stepDetails.expected": "期待値", "xpack.synthetics.stepDetails.objectCount": "オブジェクト数", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6b9b1d02ab38f..849f629a69eea 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2081,7 +2081,6 @@ "discover.gridSampleSize.description": "您正查看与您的搜索相匹配的前 {sampleSize} 个文档。要更改此值,请转到{advancedSettingsLink}。", "discover.howToSeeOtherMatchingDocumentsDescription": "下面是与您的搜索匹配的前 {sampleSize} 个文档,请优化您的搜索以查看其他文档。", "discover.noMatchRoute.bannerText": "Discover 应用程序无法识别此路由:{route}", - "discover.noResults.tryRemovingOrDisablingFilters": "尝试删除或{disablingFiltersLink}。", "discover.pageTitleWithSavedSearch": "Discover - {savedSearchTitle}", "discover.savedSearchAliasMatchRedirect.objectNoun": "{savedSearch} 搜索", "discover.savedSearchURLConflictCallout.objectNoun": "{savedSearch} 搜索", @@ -2323,15 +2322,9 @@ "discover.localMenu.shareSearchDescription": "共享搜索", "discover.localMenu.shareTitle": "共享", "discover.noMatchRoute.bannerTitleText": "未找到页面", - "discover.noResults.adjustFilters": "调整您的筛选", - "discover.noResults.adjustSearch": "调整您的查询", - "discover.noResults.expandYourTimeRangeTitle": "展开时间范围", "discover.noResults.noDocumentsOrCheckPermissionsDescription": "确保您有权查看索引并且它们包含文档。", - "discover.noResults.queryMayNotMatchTitle": "尝试搜索更长的时间段。", "discover.noResults.searchExamples.noResultsBecauseOfError": "检索搜索结果时遇到问题", "discover.noResults.searchExamples.noResultsMatchSearchCriteriaTitle": "没有任何结果匹配您的搜索条件", - "discover.noResults.temporaryDisablingFiltersLinkText": "正临时禁用筛选", - "discover.noResults.trySearchingForDifferentCombination": "尝试搜索不同的词组合。", "discover.noResultsFound": "找不到结果", "discover.notifications.invalidTimeRangeText": "提供的时间范围无效。(自:“{from}”,至:“{to}”)", "discover.notifications.invalidTimeRangeTitle": "时间范围无效", @@ -3017,15 +3010,6 @@ "guidedOnboarding.quitGuideModal.modalDescription": "您可以随时从“帮助”菜单重新启动设置指南。", "guidedOnboarding.quitGuideModal.modalTitle": "退出本指南?", "guidedOnboarding.quitGuideModal.quitButtonLabel": "退出指南", - "guidedOnboardingPackage.gettingStarted.guideCard.stepsLabel": "{progress} 步骤", - "guidedOnboardingPackage.gettingStarted.guideCard.continueGuide.buttonLabel": "继续", - "guidedOnboardingPackage.gettingStarted.guideCard.progress.completedLabel": "已完成", - "guidedOnboardingPackage.gettingStarted.guideCard.progress.inProgressLabel": "进行中", - "guidedOnboardingPackage.gettingStarted.guideCard.search.cardDescription": "为您的网站、应用程序、工作区内容或期间的任何内容创建搜索体验。", - "guidedOnboardingPackage.gettingStarted.guideCard.search.cardTitle": "搜索我的数据", - "guidedOnboardingPackage.gettingStarted.guideCard.startGuide.buttonLabel": "查看指南", - "guidedOnboardingPackage.gettingStarted.search.betaBadgeLabel": "搜索", - "guidedOnboardingPackage.gettingStarted.search.iconName": "Enterprise Search 徽标", "home.loadTutorials.requestFailedErrorMessage": "请求失败,状态代码:{status}", "home.tutorial.addDataToKibanaDescription": "除了添加 {integrationsLink} 以外,您还可以试用样例数据或上传自己的数据。", "home.tutorial.noTutorialLabel": "无法找到教程 {tutorialId}", @@ -10075,7 +10059,6 @@ "xpack.csp.findings.distributionBar.showingPageOfTotalLabel": "正在显示第 {pageStart}-{pageEnd} 个(共 {total} 个){type}", "xpack.csp.findings.findingsTableCell.addFilterButton": "添加 {field} 筛选", "xpack.csp.findings.findingsTableCell.addNegateFilterButton": "添加 {field} 作废筛选", - "xpack.csp.findings.resourceFindings.resourceFindingsPageTitle": "{resourceName} - 结果", "xpack.csp.rules.header.rulesCountLabel": "{count, plural, other { 规则}}", "xpack.csp.rules.header.totalRulesCount": "正在显示 {rules}", "xpack.csp.rules.rulePageHeader.pageHeaderTitle": "规则 - {integrationName}", @@ -13257,7 +13240,6 @@ "xpack.fileUpload.geoUploadWizard.creatingDataView": "正在创建数据视图:{indexName}", "xpack.fileUpload.geoUploadWizard.dataIndexingStarted": "正在创建索引:{indexName}", "xpack.fileUpload.geoUploadWizard.writingToIndex": "正在写入索引:已完成 {progress}%", - "xpack.fileUpload.importComplete.failedFeaturesMsg": "无法索引 {numFailures} 个特征。", "xpack.fileUpload.importComplete.permissionFailureMsg": "您无权创建或将数据导入索引“{indexName}”。", "xpack.fileUpload.importComplete.uploadFailureMsgErrorBlock": "错误:{reason}", "xpack.fileUpload.importComplete.uploadSuccessMsg": "已索引 {numFeatures} 个特征。", @@ -13302,7 +13284,7 @@ "xpack.fleet.addIntegration.installAgentStepTitle": "这些步骤将在 Fleet 中配置和注册 Elastic 代理,以便自动部署更新并集中管理该代理。作为 Fleet 的替代方案,高级用户可以在 {standaloneLink} 中运行代理。", "xpack.fleet.addIntegration.standaloneWarning": "通过在独立模式下运行 Elastic 代理来设置集成为高级选项。如果可能,我们建议改用 {link}。", "xpack.fleet.agentActivity.completedTitle": "{nbAgents} {agents} {completedText}{offlineText}", - "xpack.fleet.agentActivity.inProgressTitle": "{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}", + "xpack.fleet.agentActivity.inProgressTitle": "{inProgressText} {nbAgents} {agents} {reassignText}{upgradeText}{failuresText}", "xpack.fleet.agentActivityFlyout.cancelledDescription": "已于 {date}取消", "xpack.fleet.agentActivityFlyout.cancelledTitle": "代理 {cancelledText} 已取消", "xpack.fleet.agentActivityFlyout.completedDescription": "完成于 {date}", @@ -29732,7 +29714,6 @@ "xpack.securitySolution.expandedValue.links.viewUserSummary": "查看用户摘要", "xpack.securitySolution.expandedValue.showTopN.showTopValues": "显示排名最前值", "xpack.securitySolution.featureCatalogueDescription": "预防、收集、检测和响应威胁,以对整个基础架构提供统一的保护。", - "xpack.securitySolution.featureRegistr.subFeatures.fileOperations": "文件操作", "xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "删除案例和注释", "xpack.securitySolution.featureRegistry.deleteSubFeatureName": "删除", "xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "案例", @@ -34076,7 +34057,6 @@ "xpack.synthetics.sourceConfiguration.heartbeatIndicesTitle": "Uptime 索引", "xpack.synthetics.sourceConfiguration.indicesSectionTitle": "索引", "xpack.synthetics.sourceConfiguration.warningStateLabel": "使用时间限制", - "xpack.synthetics.ssl": "SSL", "xpack.synthetics.stackManagement": "Stack Management", "xpack.synthetics.stepDetails.expected": "预期", "xpack.synthetics.stepDetails.objectCount": "对象计数", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.test.tsx index 67e22893eef44..0a14413e9560e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.test.tsx @@ -74,13 +74,54 @@ describe('AlertSummaryWidget', () => { expect(alertSummaryWidget.queryByTestId(TITLE_DATA_TEST_SUBJ)).toBeTruthy(); }); + it('should render AlertSummaryWidget when there is only active alerts', async () => { + useLoadAlertSummaryMock.mockImplementation(() => ({ + alertSummary: { + activeAlertCount: 1, + activeAlerts: [{ key: 1671408000000, doc_count: 1 }], + recoveredAlertCount: 0, + }, + isLoading: false, + })); + const alertSummaryWidget = renderComponent(); + + expect(alertSummaryWidget.queryByTestId('alertSummaryWidgetCompact')).toBeTruthy(); + }); + + it('should render AlertSummaryWidget compact version even when there is no active and recovered alerts', async () => { + useLoadAlertSummaryMock.mockImplementation(() => ({ + alertSummary: { + activeAlertCount: 0, + activeAlerts: [], + recoveredAlertCount: 0, + }, + isLoading: false, + })); + const alertSummaryWidget = renderComponent(); + + expect(alertSummaryWidget.queryByTestId('alertSummaryWidgetCompact')).toBeTruthy(); + }); + + it('should not render AlertSummaryWidget full-size version when there is no active and recovered alerts', async () => { + useLoadAlertSummaryMock.mockImplementation(() => ({ + alertSummary: { + activeAlertCount: 0, + activeAlerts: [], + recoveredAlertCount: 0, + }, + isLoading: false, + })); + const alertSummaryWidget = renderComponent({ fullSize: true }); + + expect(alertSummaryWidget.queryByTestId('alertSummaryWidgetFullSzie')).toBeFalsy(); + }); + it('should render AlertSummaryWidgetError when API call fails', async () => { useLoadAlertSummaryMock.mockImplementation(() => ({ alertSummary: { activeAlertCount: 0, activeAlerts: [], recoveredAlertCount: 0, - recoveredAlerts: [], }, isLoading: false, error: 'Fetch Alert Summary Failed', @@ -96,7 +137,6 @@ describe('AlertSummaryWidget', () => { activeAlertCount: 0, activeAlerts: [], recoveredAlertCount: 0, - recoveredAlerts: [], }, isLoading: true, })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx index 6b04067be1864..a6b34a77c1154 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/alert_summary/alert_summary_widget.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLoadingChart } from '@elastic/eui'; import React from 'react'; import { useLoadAlertSummary } from '../../../../hooks/use_load_alert_summary'; import { AlertSummaryWidgetProps } from '.'; @@ -33,17 +33,32 @@ export const AlertSummaryWidget = ({ timeRange, }); - if (isLoading) return ; + if (isLoading) + return ( +
+ +
+ ); if (error) return ; return fullSize ? ( - + // Only show full size version if there is data + activeAlertCount || recoveredAlertCount ? ( + + ) : null ) : ( = ({ checkpoint }) => { const dataView = await getDeprecationDataView(dataService); const field = dataView.getFieldByName(DEPRECATION_LOGS_ORIGIN_FIELD); - let filters: PhraseFilter[] = []; + let filters: PhrasesFilter[] = []; if (field !== undefined) { const filter = buildPhrasesFilter(field!, [...APPS_WITH_DEPRECATION_LOGS], dataView); diff --git a/x-pack/test/accessibility/apps/transform.ts b/x-pack/test/accessibility/apps/transform.ts index fa54ea4ad6766..417ab317218de 100644 --- a/x-pack/test/accessibility/apps/transform.ts +++ b/x-pack/test/accessibility/apps/transform.ts @@ -112,6 +112,17 @@ export default function ({ getService }: FtrProviderContext) { ); await transform.sourceSelection.selectSource(ecIndexPattern); + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); + + await transform.testExecution.logTestStep('displays an empty index preview'); + await transform.wizard.assertIndexPreviewEmpty(); + + await transform.testExecution.logTestStep(`sets the date picker to '10 Years ago'`); + await transform.datePicker.quickSelect(); + await transform.testExecution.logTestStep('loads the index preview'); await transform.wizard.assertIndexPreviewLoaded(); await transform.testExecution.logTestStep('displays an empty transform preview'); @@ -191,6 +202,18 @@ export default function ({ getService }: FtrProviderContext) { 'selects the source data and loads the Transform wizard page' ); await transform.sourceSelection.selectSource(ecIndexPattern); + + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); + + await transform.testExecution.logTestStep('displays an empty index preview'); + await transform.wizard.assertIndexPreviewEmpty(); + + await transform.testExecution.logTestStep(`sets the date picker to '10 Years ago'`); + await transform.datePicker.quickSelect(); + await transform.wizard.assertIndexPreviewLoaded(); await transform.wizard.assertTransformPreviewEmpty(); diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index 6ee90026d9821..0233568b6e0d2 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -26,6 +26,7 @@ interface CreateTestConfigOptions { rejectUnauthorized?: boolean; // legacy emailDomainsAllowed?: string[]; testFiles?: string[]; + reportName?: string; useDedicatedTaskRunner: boolean; } @@ -47,6 +48,7 @@ const enabledActionTypes = [ '.tines', '.webhook', '.xmatters', + '.torq', 'test.sub-action-connector', 'test.sub-action-connector-without-sub-actions', 'test.authorization', @@ -72,6 +74,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) rejectUnauthorized = true, // legacy emailDomainsAllowed = undefined, testFiles = undefined, + reportName = undefined, useDedicatedTaskRunner, } = options; @@ -153,7 +156,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) servers, services, junit: { - reportName: 'X-Pack Alerting API Integration Tests', + reportName: reportName ? reportName : 'X-Pack Alerting API Integration Tests', }, esTestCluster: { ...xPackApiIntegrationTestsConfig.get('esTestCluster'), diff --git a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/plugin.ts b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/plugin.ts index e629b4f4a0309..a37588c7fb8de 100644 --- a/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/plugins/actions_simulators/server/plugin.ts @@ -25,6 +25,7 @@ import { initPlugin as initSlack } from './slack_simulation'; import { initPlugin as initWebhook } from './webhook_simulation'; import { initPlugin as initMSExchange } from './ms_exchage_server_simulation'; import { initPlugin as initXmatters } from './xmatters_simulation'; +import { initPlugin as initTorq } from './torq_simulation'; import { initPlugin as initUnsecuredAction } from './unsecured_actions_simulation'; import { initPlugin as initTines } from './tines_simulation'; @@ -41,6 +42,7 @@ export enum ExternalServiceSimulator { WEBHOOK = 'webhook', MS_EXCHANGE = 'exchange', XMATTERS = 'xmatters', + TORQ = 'torq', TINES = 'tines', } @@ -137,6 +139,7 @@ export class FixturePlugin implements Plugin, + res: KibanaResponseFactory + ): Promise> { + if (!validateTorqToken(req)) { + return jsonErrorResponse(res, 401, new Error('unauthorised')); + } + const { body } = req; + const content = body?.msg; + switch (content) { + case 'respond-with-400': + return jsonErrorResponse(res, 400, new Error(content)); + case 'respond-with-404': + return jsonErrorResponse(res, 404, new Error(content)); + case 'respond-with-429': + return jsonErrorResponse(res, 429, new Error(content)); + case 'respond-with-405': + return jsonErrorResponse(res, 405, new Error(content)); + case 'respond-with-502': + return jsonErrorResponse(res, 502, new Error(content)); + } + return jsonResponse(res, 204, { + status: 'success', + }); + } + ); +} + +function validateTorqToken(req: KibanaRequest): boolean { + return req.headers['x-torq-token'] === 'someRandomToken'; +} + +function jsonResponse( + res: KibanaResponseFactory, + code: number, + object: Record = {} +) { + return res.custom>({ body: object, statusCode: code }); +} + +function jsonErrorResponse(res: KibanaResponseFactory, code: number, object: Error) { + return res.custom({ body: object, statusCode: code }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/torq.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/torq.ts new file mode 100644 index 0000000000000..cad9d65e71467 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/connector_types/torq.ts @@ -0,0 +1,245 @@ +/* + * 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 httpProxy from 'http-proxy'; +import expect from '@kbn/expect'; + +import { getHttpProxyServer } from '@kbn/alerting-api-integration-helpers/get_proxy_server'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { + getExternalServiceSimulatorPath, + ExternalServiceSimulator, +} from '../../../../../common/plugins/actions_simulators/server/plugin'; + +// eslint-disable-next-line import/no-default-export +export default function torqTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const kibanaServer = getService('kibanaServer'); + const configService = getService('config'); + + describe('Torq action', () => { + let simulatedActionId = ''; + let torqSimulatorURL: string = ''; + let proxyServer: httpProxy | undefined; + let proxyHaveBeenCalled = false; + + // need to wait for kibanaServer to settle ... + before(async () => { + torqSimulatorURL = kibanaServer.resolveUrl( + getExternalServiceSimulatorPath(ExternalServiceSimulator.TORQ) + ); + proxyServer = await getHttpProxyServer( + kibanaServer.resolveUrl('/'), + configService.get('kbnTestServer.serverArgs'), + () => { + proxyHaveBeenCalled = true; + } + ); + }); + + it('Torq connector invalid token', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A Torq action', + connector_type_id: '.torq', + config: { + webhookIntegrationUrl: torqSimulatorURL, + }, + secrets: { + token: 'invalidToken', + }, + }) + .expect(200); + const { body: result } = await supertest + .post(`/api/actions/connector/${createdAction.id}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "test"}`, + }, + }) + .expect(200); + expect(result.status).to.eql('error'); + expect(result.message).to.match(/error triggering Torq workflow, unauthorised/); + }); + + it('Torq connector can be executed with token', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A Torq action', + connector_type_id: '.torq', + config: { + webhookIntegrationUrl: torqSimulatorURL, + }, + secrets: { + token: 'someRandomToken', + }, + }) + .expect(200); + + expect(createdAction).to.eql({ + id: createdAction.id, + is_preconfigured: false, + is_deprecated: false, + name: 'A Torq action', + connector_type_id: '.torq', + is_missing_secrets: false, + config: { + webhookIntegrationUrl: torqSimulatorURL, + }, + }); + + expect(typeof createdAction.id).to.be('string'); + }); + + it('should return unsuccessfully when default Torq webhookIntegrationUrl is not present in allowedHosts', async () => { + await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A Torq action', + connector_type_id: '.torq', + config: { + webhookIntegrationUrl: 'https://test.torq.io/v1/something', + }, + }) + .expect(400) + .then((resp: any) => { + expect(resp.body).to.eql({ + statusCode: 400, + error: 'Bad Request', + message: + 'error validating action type config: error configuring send to Torq action: target url "https://test.torq.io/v1/something" is not added to the Kibana config xpack.actions.allowedHosts', + }); + }); + }); + + it('should create Torq simulator action successfully', async () => { + const { body: createdSimulatedAction } = await supertest + .post('/api/actions/connector') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A Torq simulator', + connector_type_id: '.torq', + config: { + webhookIntegrationUrl: torqSimulatorURL, + }, + secrets: { + token: 'someRandomToken', + }, + }) + .expect(200); + + simulatedActionId = createdSimulatedAction.id; + }); + + it('should handle executing with a simulated success', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "test"}`, + }, + }) + .expect(200); + + expect(proxyHaveBeenCalled).to.equal(true); + expect(result).to.eql({ + status: 'ok', + connector_id: simulatedActionId, + data: `{"msg": "test"}`, + }); + }); + + it('should handle a 400 Torq error', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "respond-with-400"}`, + }, + }) + .expect(200); + expect(result.status).to.equal('error'); + expect(result.message).to.match(/error triggering Torq workflow, invalid response/); + }); + + it('should handle a 404 Torq error', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "respond-with-404"}`, + }, + }) + .expect(200); + expect(result.status).to.equal('error'); + expect(result.message).to.match( + /error triggering Torq workflow, make sure the webhook URL is valid/ + ); + }); + + it('should handle a 429 Torq error', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "respond-with-429"}`, + }, + }) + .expect(200); + + expect(result.status).to.equal('error'); + expect(result.message).to.match(/error triggering Torq workflow, retry later/); + }); + + it('should handle a 500 Torq error', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "respond-with-502"}`, + }, + }) + .expect(200); + + expect(result.status).to.equal('error'); + expect(result.message).to.match(/error triggering Torq workflow, retry later/); + expect(result.retry).to.equal(true); + }); + + it('should handle a 405 Torq error', async () => { + const { body: result } = await supertest + .post(`/api/actions/connector/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + body: `{"msg": "respond-with-405"}`, + }, + }) + .expect(200); + + expect(result.status).to.equal('error'); + expect(result.message).to.match(/error triggering Torq workflow, method is not supported/); + }); + + after(() => { + if (proxyServer) { + proxyServer.close(); + } + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts index a96ff29767b0e..66f83daff0429 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/index.ts @@ -36,6 +36,7 @@ export default function connectorsTests({ loadTestFile, getService }: FtrProvide loadTestFile(require.resolve('./connector_types/webhook')); loadTestFile(require.resolve('./connector_types/xmatters')); loadTestFile(require.resolve('./connector_types/tines')); + loadTestFile(require.resolve('./connector_types/torq')); loadTestFile(require.resolve('./create')); loadTestFile(require.resolve('./delete')); loadTestFile(require.resolve('./execute')); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/event_log.ts index f16c04565872b..bf3f60a9a9c55 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/event_log.ts @@ -9,15 +9,14 @@ import expect from '@kbn/expect'; import { Spaces } from '../../../scenarios'; import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { validateEvent } from '../../../../spaces_only/tests/alerting/event_log'; +import { validateEvent } from '../../../../spaces_only/tests/alerting/group1/event_log'; // eslint-disable-next-line import/no-default-export export default function eventLogTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const retry = getService('retry'); - // FLAKY: https://github.com/elastic/kibana/issues/147512 - describe.skip('eventLog', () => { + describe('eventLog', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); @@ -65,13 +64,13 @@ export default function eventLogTests({ getService }: FtrProviderContext) { const errorEvents = someEvents.filter( (event) => event?.kibana?.alerting?.status === 'error' ); - if (errorEvents.length === 0) { - throw new Error('no execute/error events yet'); + if (errorEvents.length < 2) { + throw new Error('not enough execute/error events yet'); } return errorEvents; }); - const event = events[0]; + const event = events[1]; expect(event).to.be.ok(); validateEvent(event, { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/config.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/config.ts new file mode 100644 index 0000000000000..b95ab7aec7d4b --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/config.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 { createTestConfig } from '../../../common/config'; + +export const EmailDomainsAllowed = ['example.org', 'test.com']; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('spaces_only', { + disabledPlugins: ['security'], + license: 'trial', + enableActionsProxy: false, + verificationMode: 'none', + customizeLocalHostSsl: true, + preconfiguredAlertHistoryEsIndex: true, + emailDomainsAllowed: EmailDomainsAllowed, + useDedicatedTaskRunner: true, + testFiles: [require.resolve('.')], + reportName: 'X-Pack Alerting API Integration Tests - Action Task Params', +}); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts index 115358c4bce3a..ab0c837c60938 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts @@ -6,7 +6,7 @@ */ import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { buildUp, tearDown } from '..'; +import { buildUp, tearDown } from '../helpers'; // eslint-disable-next-line import/no-default-export export default function actionTaskParamsTests({ loadTestFile, getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts index 3de2f5165d03e..830f5e6f8d96d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/check_registered_connector_types.ts @@ -44,6 +44,7 @@ export default function createRegisteredConnectorTypeTests({ getService }: FtrPr '.resilient', '.teams', '.tines', + '.torq', '.opsgenie', ].sort() ); diff --git a/x-pack/test/alerting_api_integration/spaces_only/config.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/config.ts similarity index 80% rename from x-pack/test/alerting_api_integration/spaces_only/config.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/actions/config.ts index 988a6effd5639..3274d91ceb732 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/config.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { createTestConfig } from '../common/config'; +import { createTestConfig } from '../../../common/config'; export const EmailDomainsAllowed = ['example.org', 'test.com']; @@ -19,4 +19,6 @@ export default createTestConfig('spaces_only', { preconfiguredAlertHistoryEsIndex: true, emailDomainsAllowed: EmailDomainsAllowed, useDedicatedTaskRunner: true, + testFiles: [require.resolve('.')], + reportName: 'X-Pack Alerting API Integration Tests - Actions', }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types/stack/email.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types/stack/email.ts index eb2ddc33ce444..9bb924fd5c945 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types/stack/email.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/connector_types/stack/email.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { ObjectRemover } from '../../../../../common/lib'; -import { EmailDomainsAllowed } from '../../../../config'; +import { EmailDomainsAllowed } from '../../config'; const EmailDomainAllowed = EmailDomainsAllowed[EmailDomainsAllowed.length - 1]; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts index a12ca249e0d98..1e8489c6f901c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/index.ts @@ -6,7 +6,7 @@ */ import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { buildUp, tearDown } from '..'; +import { buildUp, tearDown } from '../helpers'; // eslint-disable-next-line import/no-default-export export default function actionsTests({ loadTestFile, getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/lib/create_test_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create_test_data.ts similarity index 100% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/lib/create_test_data.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create_test_data.ts diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts index 602bf75a7ef2b..52d3907eb88d0 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAggregateTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate_post.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate_post.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts index 05d7701a23e54..378b3cb405561 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/aggregate_post.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/aggregate_post.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAggregateTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts index b1e6ec2b2d458..868d3f24f50e2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/alerts_base.ts @@ -13,8 +13,8 @@ import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; import { TaskRunning, TaskRunningStage } from '@kbn/task-manager-plugin/server/task_running'; import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Space } from '../../../common/types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Space } from '../../../../common/types'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { getUrlPrefix, getTestRuleData, @@ -22,7 +22,7 @@ import { AlertUtils, ensureDatetimeIsWithinRange, TaskManagerUtils, -} from '../../../common/lib'; +} from '../../../../common/lib'; export function alertTests({ getService }: FtrProviderContext, space: Space) { const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.ts new file mode 100644 index 0000000000000..ef4c38d9171af --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/config.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 { createTestConfig } from '../../../../common/config'; + +export const EmailDomainsAllowed = ['example.org', 'test.com']; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('spaces_only', { + disabledPlugins: ['security'], + license: 'trial', + enableActionsProxy: false, + verificationMode: 'none', + customizeLocalHostSsl: true, + preconfiguredAlertHistoryEsIndex: true, + emailDomainsAllowed: EmailDomainsAllowed, + useDedicatedTaskRunner: true, + testFiles: [require.resolve('.')], + reportName: 'X-Pack Alerting API Integration Tests - Alerting - group1', +}); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts index 5e07ec90aa7c3..534df486281be 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { SavedObject } from '@kbn/core/server'; import { RawRule } from '@kbn/alerting-plugin/server/types'; -import { Spaces } from '../../scenarios'; +import { Spaces } from '../../../scenarios'; import { checkAAD, getUrlPrefix, @@ -16,8 +16,8 @@ import { ObjectRemover, getConsumerUnauthorizedErrorMessage, TaskManagerDoc, -} from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAlertTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/delete.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/delete.ts index 073d76dc859a5..ba23571927120 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/delete.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createDeleteTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts index d4149c9cf2fb8..1ff7aab8f4ca6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/disable.ts @@ -6,8 +6,8 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils as RuleUtils, checkAAD, @@ -16,7 +16,7 @@ import { ObjectRemover, getEventLog, TaskManagerDoc, -} from '../../../common/lib'; +} from '../../../../common/lib'; import { validateEvent } from './event_log'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/enable.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/enable.ts index d8dec2a486298..f4ad874d3357e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/enable.ts @@ -6,8 +6,8 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, @@ -15,7 +15,7 @@ import { getTestRuleData, ObjectRemover, TaskManagerDoc, -} from '../../../common/lib'; +} from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createEnableAlertTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 73968703bf9bb..9a7d90d4adf24 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function eventLogTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts index bcf1a3d09ca50..30b3643466f42 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/find.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { SuperTest, Test } from 'supertest'; import { fromKueryExpression } from '@kbn/es-query'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; async function createAlert( objectRemover: ObjectRemover, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts index 6cd28d0202465..dd6b7b9327d27 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { SuperTest, Test } from 'supertest'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; const getTestUtils = ( describeType: 'internal' | 'public', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts index 0f2e67b1187fc..dbb8cee8673b8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_action_error_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_action_error_log.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createGetActionErrorLogTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_state.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_state.ts index 61d38b522bb59..6082e6ff69eb8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_state.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, ObjectRemover, getTestRuleData } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, ObjectRemover, getTestRuleData } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createGetAlertStateTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_summary.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_summary.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts index 1ec570f6c4f71..712616329929a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_alert_summary.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_alert_summary.ts @@ -8,15 +8,15 @@ import expect from '@kbn/expect'; import { omit } from 'lodash'; -import { Spaces } from '../../scenarios'; +import { Spaces } from '../../../scenarios'; import { getUrlPrefix, ObjectRemover, getTestRuleData, AlertUtils, getEventLog, -} from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createGetAlertSummaryTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_execution_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_execution_log.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_execution_log.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_execution_log.ts index 0d2607ad0602b..b7d2a7f03b3e6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get_execution_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/get_execution_log.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createGetExecutionLogTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts new file mode 100644 index 0000000000000..87ef8228c7303 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/index.ts @@ -0,0 +1,32 @@ +/* + * 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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { buildUp, tearDown } from '../../helpers'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Alerting', () => { + before(async () => await buildUp(getService)); + after(async () => await tearDown(getService)); + + loadTestFile(require.resolve('./aggregate')); + loadTestFile(require.resolve('./aggregate_post')); + loadTestFile(require.resolve('./create')); + loadTestFile(require.resolve('./delete')); + loadTestFile(require.resolve('./disable')); + loadTestFile(require.resolve('./enable')); + loadTestFile(require.resolve('./find')); + loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./get_alert_state')); + loadTestFile(require.resolve('./get_alert_summary')); + loadTestFile(require.resolve('./get_execution_log')); + loadTestFile(require.resolve('./get_action_error_log')); + loadTestFile(require.resolve('./rule_types')); + loadTestFile(require.resolve('./event_log')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts index c3d2f11c580e7..2f2bdde9e7455 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix } from '../../../common/lib/space_test_utils'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix } from '../../../../common/lib/space_test_utils'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function listAlertTypes({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_default_space.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/alerts_default_space.ts similarity index 70% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_default_space.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/alerts_default_space.ts index 08c269239bd13..1e62dd82bdaa8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_default_space.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/alerts_default_space.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { Spaces } from '../../scenarios'; -import { alertTests } from './alerts_base'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { alertTests } from '../group1/alerts_base'; // eslint-disable-next-line import/no-default-export export default function alertSpace1Tests(context: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_space1.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/alerts_space1.ts similarity index 70% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_space1.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/alerts_space1.ts index 36736065a6fc8..071b3c7d13b4e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_space1.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/alerts_space1.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { Spaces } from '../../scenarios'; -import { alertTests } from './alerts_base'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { alertTests } from '../group1/alerts_base'; // eslint-disable-next-line import/no-default-export export default function alertSpace1Tests(context: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.ts new file mode 100644 index 0000000000000..9aa68299dbdac --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/config.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 { createTestConfig } from '../../../../common/config'; + +export const EmailDomainsAllowed = ['example.org', 'test.com']; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('spaces_only', { + disabledPlugins: ['security'], + license: 'trial', + enableActionsProxy: false, + verificationMode: 'none', + customizeLocalHostSsl: true, + preconfiguredAlertHistoryEsIndex: true, + emailDomainsAllowed: EmailDomainsAllowed, + useDedicatedTaskRunner: true, + testFiles: [require.resolve('.')], + reportName: 'X-Pack Alerting API Integration Tests - Alerting - group2', +}); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/execution_status.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/execution_status.ts index 02edd1d4f37c8..a21027ea448b9 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/execution_status.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; +import { Spaces } from '../../../scenarios'; import { checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, ensureDatetimesAreOrdered, -} from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function executionStatusAlertTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/index.ts new file mode 100644 index 0000000000000..1ccbb1c8f722d --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/index.ts @@ -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 { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { buildUp, tearDown } from '../../helpers'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Alerting', () => { + before(async () => await buildUp(getService)); + after(async () => await tearDown(getService)); + + loadTestFile(require.resolve('./execution_status')); + loadTestFile(require.resolve('./monitoring_collection')); + loadTestFile(require.resolve('./monitoring')); + loadTestFile(require.resolve('./mute_all')); + loadTestFile(require.resolve('./mute_instance')); + loadTestFile(require.resolve('./unmute_all')); + loadTestFile(require.resolve('./unmute_instance')); + loadTestFile(require.resolve('./update')); + loadTestFile(require.resolve('./update_api_key')); + loadTestFile(require.resolve('./alerts_space1')); + loadTestFile(require.resolve('./alerts_default_space')); + loadTestFile(require.resolve('./transform_rule_types')); + loadTestFile(require.resolve('./ml_rule_types')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/anomaly_detection/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/anomaly_detection/alert.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts index 51db0e9055faf..1d69200a277d4 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/anomaly_detection/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/alert.ts @@ -13,9 +13,9 @@ import { MlAnomalyDetectionAlertParams } from '@kbn/ml-plugin/common/types/alert import { ANOMALY_SCORE_MATCH_GROUP_ID } from '@kbn/ml-plugin/server/lib/alerts/register_anomaly_detection_alert_type'; import { ML_ALERT_TYPES } from '@kbn/ml-plugin/common/constants/alerts'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { getUrlPrefix, ObjectRemover } from '../../../../../common/lib'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { Spaces } from '../../../../../scenarios'; +import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; const ACTION_TYPE_ID = '.index'; const ALERT_TYPE_ID = ML_ALERT_TYPES.ANOMALY_DETECTION; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/anomaly_detection/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/index.ts similarity index 85% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/anomaly_detection/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/index.ts index f2875c62c67cd..392068317295d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/anomaly_detection/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/anomaly_detection/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/index.ts similarity index 86% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/index.ts index 1b7a2ea1842ee..005e5bce7adea 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ml_rule_types/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/ml_rule_types/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/monitoring.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/monitoring.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts index 38506eb54a4bc..95d2a08a57d33 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/monitoring.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function monitoringAlertTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/monitoring_collection.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/monitoring_collection.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts index c6f240b8bb6bd..e4e52d6af314a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/monitoring_collection.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/monitoring_collection.ts @@ -7,16 +7,16 @@ import expect from '@kbn/expect'; import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../scenarios'; +import { Spaces } from '../../../scenarios'; import { getUrlPrefix, getTestRuleData, ObjectRemover, createWaitForExecutionCount, getEventLog, -} from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { createEsDocuments } from './builtin_alert_types/lib/create_test_data'; +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { createEsDocuments } from '../create_test_data'; const NODE_RULES_MONITORING_COLLECTION_URL = `/api/monitoring_collection/node_rules`; const CLUSTER_RULES_MONITORING_COLLECTION_URL = `/api/monitoring_collection/cluster_rules`; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/mute_all.ts similarity index 94% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/mute_all.ts index 3fe13f8debe25..9cfbb1e477526 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/mute_all.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, -} from '../../../common/lib'; +} from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createMuteTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/mute_instance.ts similarity index 94% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/mute_instance.ts index d32b74fd39447..85dda60babfd0 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/mute_instance.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, -} from '../../../common/lib'; +} from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createMuteInstanceTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/index.ts similarity index 86% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/index.ts index f743df169d417..6c50e058d9702 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/transform_health/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/alert.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/transform_health/alert.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/alert.ts index ee432431b7c83..881f8152bcd2d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/transform_health/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/alert.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { PutTransformsRequestSchema } from '@kbn/transform-plugin/common/api_schemas/transforms'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover } from '../../../../../common/lib'; -import { Spaces } from '../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; +import { Spaces } from '../../../../../scenarios'; const ACTION_TYPE_ID = '.index'; const ALERT_TYPE_ID = 'transform_health'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/transform_health/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts similarity index 85% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/transform_health/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts index c324745b85813..adc93332a0f56 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/transform_rule_types/transform_health/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/transform_rule_types/transform_health/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/unmute_all.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/unmute_all.ts index 47f61250157a3..a39f576364d04 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/unmute_all.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, -} from '../../../common/lib'; +} from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createUnmuteTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/unmute_instance.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/unmute_instance.ts index 086f40d9febae..3dda4b0db49b8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/unmute_instance.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, -} from '../../../common/lib'; +} from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createUnmuteInstanceTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts index 21008836bf5eb..20239f94cfef3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createUpdateTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update_api_key.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update_api_key.ts index 9fe5c7e112c79..a57d9dc90fd6d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group2/update_api_key.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, -} from '../../../common/lib'; +} from '../../../../common/lib'; /** * Eventhough security is disabled, this test checks the API behavior. diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/common.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts similarity index 93% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/common.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts index faca906abc586..9ec5171d74d5c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/common.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/common.ts @@ -6,10 +6,10 @@ */ import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { Spaces } from '../../../../scenarios'; -import { getUrlPrefix, ObjectRemover } from '../../../../../common/lib'; -import { createEsDocuments, createEsDocumentsWithGroups } from '../lib/create_test_data'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { Spaces } from '../../../../../scenarios'; +import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; +import { createEsDocuments, createEsDocumentsWithGroups } from '../../../create_test_data'; export const RULE_TYPE_ID = '.es-query'; export const CONNECTOR_TYPE_ID = '.index'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts similarity index 86% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts index 98c0737ba670c..a584753db7c25 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/query_dsl_only.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/query_dsl_only.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/query_dsl_only.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/query_dsl_only.ts index 6024d07bccec0..8a558c8e27299 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/query_dsl_only.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/query_dsl_only.ts @@ -9,10 +9,10 @@ import expect from '@kbn/expect'; import { ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover } from '../../../../../common/lib'; -import { createDataStream, deleteDataStream } from '../lib/create_test_data'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; +import { createDataStream, deleteDataStream } from '../../../create_test_data'; import { createConnector, CreateRuleParams, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/rule.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts index 6fd81745f770e..6a2314bec3a0b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/rule.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/es_query/rule.ts @@ -9,9 +9,9 @@ import expect from '@kbn/expect'; import { ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover } from '../../../../../common/lib'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover } from '../../../../../../common/lib'; import { createConnector, ES_GROUPS_TO_WRITE, @@ -25,7 +25,7 @@ import { RULE_INTERVAL_SECONDS, RULE_TYPE_ID, } from './common'; -import { createDataStream, deleteDataStream } from '../lib/create_test_data'; +import { createDataStream, deleteDataStream } from '../../../create_test_data'; // eslint-disable-next-line import/no-default-export export default function ruleTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index.ts new file mode 100644 index 0000000000000..5489af62f52af --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { FtrProviderContext } from '../../../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile }: FtrProviderContext) { + describe('builtin alertTypes', () => { + loadTestFile(require.resolve('./index_threshold')); + loadTestFile(require.resolve('./es_query')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts index 945e9f5cd7380..46f8a0dc955f3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/alert.ts @@ -9,11 +9,11 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../common/lib'; -import { createEsDocumentsWithGroups } from '../lib/create_test_data'; -import { createDataStream, deleteDataStream } from '../lib/create_test_data'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../../common/lib'; +import { createEsDocumentsWithGroups } from '../../../create_test_data'; +import { createDataStream, deleteDataStream } from '../../../create_test_data'; const RULE_TYPE_ID = '.index-threshold'; const CONNECTOR_TYPE_ID = '.index'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/fields_endpoint.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/fields_endpoint.ts index 48ba628fd788e..bedbd386f9ef3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/fields_endpoint.ts @@ -9,9 +9,9 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix } from '../../../../../common/lib'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix } from '../../../../../../common/lib'; const API_URI = 'internal/triggers_actions_ui/data/_fields'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/index.ts similarity index 88% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/index.ts index 0030e7a11581d..454daa02b79f3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/indices_endpoint.ts similarity index 93% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/indices_endpoint.ts index 5c6c11d6efd7b..e6c0e96e67812 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/indices_endpoint.ts @@ -9,11 +9,11 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix } from '../../../../../common/lib'; -import { createEsDocumentsWithGroups } from '../lib/create_test_data'; -import { createDataStream, deleteDataStream } from '../lib/create_test_data'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix } from '../../../../../../common/lib'; +import { createEsDocumentsWithGroups } from '../../../create_test_data'; +import { createDataStream, deleteDataStream } from '../../../create_test_data'; const API_URI = 'internal/triggers_actions_ui/data/_indices'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/time_series_query_endpoint.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/time_series_query_endpoint.ts index ec342baf3ad66..24ec06cb9874e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/builtin_alert_types/index_threshold/time_series_query_endpoint.ts @@ -10,11 +10,11 @@ import expect from '@kbn/expect'; import { TimeSeriesQuery } from '@kbn/triggers-actions-ui-plugin/server'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix } from '../../../../../common/lib'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix } from '../../../../../../common/lib'; -import { createEsDocumentsWithGroups } from '../lib/create_test_data'; +import { createEsDocumentsWithGroups } from '../../../create_test_data'; const INDEX_THRESHOLD_TIME_SERIES_QUERY_URL = 'internal/triggers_actions_ui/data/_time_series_query'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/config.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/config.ts new file mode 100644 index 0000000000000..c5fa4109abd3a --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/config.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 { createTestConfig } from '../../../../common/config'; + +export const EmailDomainsAllowed = ['example.org', 'test.com']; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('spaces_only', { + disabledPlugins: ['security'], + license: 'trial', + enableActionsProxy: false, + verificationMode: 'none', + customizeLocalHostSsl: true, + preconfiguredAlertHistoryEsIndex: true, + emailDomainsAllowed: EmailDomainsAllowed, + useDedicatedTaskRunner: true, + testFiles: [require.resolve('.')], + reportName: 'X-Pack Alerting API Integration Tests - Alerting - group3', +}); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/index.ts new file mode 100644 index 0000000000000..05fcb4f95d901 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group3/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { buildUp, tearDown } from '../../helpers'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Alerting', () => { + before(async () => await buildUp(getService)); + after(async () => await tearDown(getService)); + + loadTestFile(require.resolve('./builtin_alert_types')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_as_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_as_data.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts index 13cb9bcd337f9..49ec03fc6d8d8 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_as_data.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data.ts @@ -8,7 +8,7 @@ import { alertFieldMap } from '@kbn/alerting-plugin/common/alert_schema'; import { mappingFromFieldMap } from '@kbn/alerting-plugin/common/alert_schema/field_maps/mapping_from_field_map'; import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createAlertsAsDataTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/auto_recover/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/auto_recover/index.ts similarity index 85% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/auto_recover/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/auto_recover/index.ts index 619f6e9b4c93e..529dfd5f30fad 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/auto_recover/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/auto_recover/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/auto_recover/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/auto_recover/rule.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/auto_recover/rule.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/auto_recover/rule.ts index d46c2d2c60958..0448472670e3e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/auto_recover/rule.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/auto_recover/rule.ts @@ -9,10 +9,10 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, TaskManagerUtils } from '../../../../../common/lib'; -import { createEsDocuments } from '../lib/create_test_data'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover, TaskManagerUtils } from '../../../../../../common/lib'; +import { createEsDocuments } from '../../../create_test_data'; const RULE_INTERVAL_SECONDS = 6; const RULE_INTERVALS_TO_WRITE = 5; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/cancellable/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/cancellable/index.ts similarity index 85% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/cancellable/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/cancellable/index.ts index a0b500ef69024..45e495649f7db 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/cancellable/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/cancellable/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/cancellable/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/cancellable/rule.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/cancellable/rule.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/cancellable/rule.ts index f8a84727348fb..41a12004e256a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/cancellable/rule.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/cancellable/rule.ts @@ -8,10 +8,10 @@ import expect from '@kbn/expect'; import { ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../common/lib'; -import { createEsDocuments } from '../lib/create_test_data'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../../common/lib'; +import { createEsDocuments } from '../../../create_test_data'; const RULE_INTERVAL_SECONDS = 6; const RULE_INTERVALS_TO_WRITE = 5; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/alert_limit_services.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/alert_limit_services.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/alert_limit_services.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/alert_limit_services.ts index 00b607c102a3a..3d5eb4004ca94 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/alert_limit_services.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/alert_limit_services.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../common/lib'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function maxAlertsRuleTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index.ts similarity index 91% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index.ts index 5f5e6f0e75c0c..4b58c668bc3e9 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingCircuitBreakerTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts index 63a5e5283a10b..8bab44baee538 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/circuit_breaker/index_threshold_max_alerts.ts @@ -8,10 +8,10 @@ import expect from '@kbn/expect'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../common/lib'; -import { createEsDocumentsWithGroups } from '../lib/create_test_data'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../../common/lib'; +import { createEsDocumentsWithGroups } from '../../../create_test_data'; const RULE_INTERVAL_SECONDS = 6; const RULE_INTERVALS_TO_WRITE = 1; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/index.ts similarity index 78% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/index.ts index 83c77b504833e..6db90f566b230 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/index.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { describe('builtin alertTypes', () => { - loadTestFile(require.resolve('./index_threshold')); - loadTestFile(require.resolve('./es_query')); loadTestFile(require.resolve('./long_running')); loadTestFile(require.resolve('./cancellable')); loadTestFile(require.resolve('./circuit_breaker')); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/long_running/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/long_running/index.ts similarity index 85% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/long_running/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/long_running/index.ts index d997cebc6fd8d..224c3c99046c0 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/long_running/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/long_running/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/long_running/rule.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/long_running/rule.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/long_running/rule.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/long_running/rule.ts index 580b058d3ab1e..fb64991d1b2a3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/long_running/rule.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/builtin_alert_types/long_running/rule.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; -import { Spaces } from '../../../../scenarios'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../common/lib'; +import { Spaces } from '../../../../../scenarios'; +import { FtrProviderContext } from '../../../../../../common/ftr_provider_context'; +import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../../../../common/lib'; const RULE_INTERVAL_SECONDS = 3; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/bulk_edit.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/bulk_edit.ts index 0fcee156b8047..fa039cc3f0533 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/bulk_edit.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/bulk_edit.ts @@ -8,15 +8,15 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; import type { SanitizedRule } from '@kbn/alerting-plugin/common'; -import { Spaces } from '../../scenarios'; +import { Spaces } from '../../../scenarios'; import { checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, createWaitForExecutionCount, -} from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; const getSnoozeSchedule = () => { return { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/capped_action_type.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/capped_action_type.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/capped_action_type.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/capped_action_type.ts index 374dbddfc17b4..356812a330b30 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/capped_action_type.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/capped_action_type.ts @@ -6,9 +6,9 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { getEventLog, getTestRuleData, getUrlPrefix, ObjectRemover } from '../../../common/lib'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getEventLog, getTestRuleData, getUrlPrefix, ObjectRemover } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createCappedActionsTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts index 125f5ee4e761d..042db04fe4e14 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/check_registered_rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/check_registered_rule_types.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createRegisteredRuleTypeTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/config.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/config.ts new file mode 100644 index 0000000000000..8ceb21f6c9b5f --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/config.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 { createTestConfig } from '../../../../common/config'; + +export const EmailDomainsAllowed = ['example.org', 'test.com']; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig('spaces_only', { + disabledPlugins: ['security'], + license: 'trial', + enableActionsProxy: false, + verificationMode: 'none', + customizeLocalHostSsl: true, + preconfiguredAlertHistoryEsIndex: true, + emailDomainsAllowed: EmailDomainsAllowed, + useDedicatedTaskRunner: true, + testFiles: [require.resolve('.')], + reportName: 'X-Pack Alerting API Integration Tests - Alerting - group4', +}); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ephemeral.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/ephemeral.ts similarity index 96% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ephemeral.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/ephemeral.ts index 26c9829c9ef35..c658e20f8108f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/ephemeral.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/ephemeral.ts @@ -10,9 +10,9 @@ import { flatten } from 'lodash'; import { IValidatedEvent } from '@kbn/event-log-plugin/server'; import { DEFAULT_MAX_EPHEMERAL_ACTIONS_PER_ALERT } from '@kbn/alerting-plugin/server/config'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createNotifyWhenTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log_alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log_alerts.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts index e5d69507e8c6b..f76877dd28511 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log_alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/event_log_alerts.ts @@ -7,16 +7,17 @@ import expect from '@kbn/expect'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function eventLogAlertTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const retry = getService('retry'); - describe('eventLog alerts', () => { + // FLAKY: https://github.com/elastic/kibana/issues/150071 + describe.skip('eventLog alerts', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/flapping_history.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/flapping_history.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/flapping_history.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/flapping_history.ts index ac09451e862fb..efc4019b58329 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/flapping_history.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/flapping_history.ts @@ -7,9 +7,9 @@ import expect from '@kbn/expect'; import { get } from 'lodash'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { Spaces } from '../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { Spaces } from '../../../scenarios'; // eslint-disable-next-line import/no-default-export export default function createFlappingHistoryTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts new file mode 100644 index 0000000000000..8fb5c46d623b1 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { buildUp, tearDown } from '../../helpers'; + +// eslint-disable-next-line import/no-default-export +export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Alerting', () => { + before(async () => await buildUp(getService)); + after(async () => await tearDown(getService)); + + loadTestFile(require.resolve('./builtin_alert_types')); + loadTestFile(require.resolve('./mustache_templates.ts')); + loadTestFile(require.resolve('./notify_when')); + loadTestFile(require.resolve('./ephemeral')); + loadTestFile(require.resolve('./event_log_alerts')); + loadTestFile(require.resolve('./snooze')); + loadTestFile(require.resolve('./bulk_edit')); + loadTestFile(require.resolve('./capped_action_type')); + loadTestFile(require.resolve('./scheduled_task_id')); + loadTestFile(require.resolve('./run_soon')); + loadTestFile(require.resolve('./flapping_history')); + loadTestFile(require.resolve('./check_registered_rule_types')); + loadTestFile(require.resolve('./alerts_as_data')); + // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 + + // note that this test will destroy existing spaces + loadTestFile(require.resolve('./migrations.ts')); + loadTestFile(require.resolve('./migrations/index.ts')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts similarity index 99% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts index f41887e6e38e6..35ad717ad8096 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations.ts @@ -9,9 +9,9 @@ import expect from '@kbn/expect'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { RawRule, RawRuleAction } from '@kbn/alerting-plugin/server/types'; import { FILEBEAT_7X_INDICATOR_PATH } from '@kbn/alerting-plugin/server/saved_objects/migrations'; -import { SavedObjectReference } from '@kbn/core-saved-objects-server'; -import { getUrlPrefix } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import type { SavedObjectReference } from '@kbn/core/server'; +import { getUrlPrefix } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations/8_2_0.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations/8_2_0.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts index 6a9117a016cbf..963f856845c10 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations/8_2_0.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/8_2_0.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import type { RawRule } from '@kbn/alerting-plugin/server/types'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createGetTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/index.ts similarity index 85% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/index.ts index 59e7eed1530bf..9af28e3656a0d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/migrations/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function migrationTests({ loadTestFile, getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts index 64076aeafd14b..4663d76a988c2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mustache_templates.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/mustache_templates.ts @@ -18,13 +18,13 @@ import { URL, format as formatUrl } from 'url'; import axios from 'axios'; import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { getWebhookServer, getSlackServer, -} from '../../../common/plugins/actions_simulators/server/plugin'; +} from '../../../../common/plugins/actions_simulators/server/plugin'; // eslint-disable-next-line import/no-default-export export default function executionStatusAlertTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notify_when.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notify_when.ts index a7996f19554e8..e32813934e4c2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/notify_when.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notify_when.ts @@ -8,9 +8,9 @@ import expect from '@kbn/expect'; import { IValidatedEvent } from '@kbn/event-log-plugin/server'; -import { Spaces } from '../../scenarios'; -import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { getUrlPrefix, ObjectRemover, getTestRuleData, getEventLog } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function createNotifyWhenTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/run_soon.ts similarity index 97% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/run_soon.ts index bba958d47d241..ceaba73b2a2ac 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/run_soon.ts @@ -6,8 +6,8 @@ */ import expect from '@kbn/expect'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; const LOADED_RULE_ID = '74f3e6d7-b7bb-477d-ac28-92ee22728e6e'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/scheduled_task_id.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts similarity index 95% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/scheduled_task_id.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts index d008421381b14..d8570564d32d2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/scheduled_task_id.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/scheduled_task_id.ts @@ -6,8 +6,13 @@ */ import expect from '@kbn/expect'; -import { getUrlPrefix, TaskManagerDoc, ObjectRemover, getTestRuleData } from '../../../common/lib'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + getUrlPrefix, + TaskManagerDoc, + ObjectRemover, + getTestRuleData, +} from '../../../../common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; const MIGRATED_RULE_ID = '74f3e6d7-b7bb-477d-ac28-92ee22728e6e'; const MIGRATED_TASK_ID = '329798f0-b0b0-11ea-9510-fdf248d5f2a4'; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/snooze.ts similarity index 98% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/snooze.ts index d4ae41f547014..87360e2f12a4b 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/snooze.ts @@ -7,8 +7,8 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, @@ -16,7 +16,7 @@ import { getTestRuleData, ObjectRemover, getEventLog, -} from '../../../common/lib'; +} from '../../../../common/lib'; const NOW = new Date().toISOString(); const SNOOZE_SCHEDULE = { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unsnooze.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/unsnooze.ts similarity index 94% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unsnooze.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/unsnooze.ts index 79811debe9f46..4c3937d23190c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unsnooze.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/unsnooze.ts @@ -6,15 +6,15 @@ */ import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { AlertUtils, checkAAD, getUrlPrefix, getTestRuleData, ObjectRemover, -} from '../../../common/lib'; +} from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export export default function createSnoozeRuleTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts deleted file mode 100644 index d7da90cf56df4..0000000000000 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ /dev/null @@ -1,63 +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 { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { buildUp, tearDown } from '..'; - -// eslint-disable-next-line import/no-default-export -export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { - describe('Alerting', () => { - before(async () => await buildUp(getService)); - after(async () => await tearDown(getService)); - - loadTestFile(require.resolve('./aggregate')); - loadTestFile(require.resolve('./aggregate_post')); - loadTestFile(require.resolve('./create')); - loadTestFile(require.resolve('./delete')); - loadTestFile(require.resolve('./disable')); - loadTestFile(require.resolve('./enable')); - loadTestFile(require.resolve('./find')); - loadTestFile(require.resolve('./get')); - loadTestFile(require.resolve('./get_alert_state')); - loadTestFile(require.resolve('./get_alert_summary')); - loadTestFile(require.resolve('./get_execution_log')); - loadTestFile(require.resolve('./get_action_error_log')); - loadTestFile(require.resolve('./rule_types')); - loadTestFile(require.resolve('./event_log')); - loadTestFile(require.resolve('./execution_status')); - loadTestFile(require.resolve('./monitoring_collection')); - loadTestFile(require.resolve('./monitoring')); - loadTestFile(require.resolve('./mute_all')); - loadTestFile(require.resolve('./mute_instance')); - loadTestFile(require.resolve('./unmute_all')); - loadTestFile(require.resolve('./unmute_instance')); - loadTestFile(require.resolve('./update')); - loadTestFile(require.resolve('./update_api_key')); - loadTestFile(require.resolve('./alerts_space1')); - loadTestFile(require.resolve('./alerts_default_space')); - loadTestFile(require.resolve('./builtin_alert_types')); - loadTestFile(require.resolve('./transform_rule_types')); - loadTestFile(require.resolve('./ml_rule_types')); - loadTestFile(require.resolve('./mustache_templates.ts')); - loadTestFile(require.resolve('./notify_when')); - loadTestFile(require.resolve('./ephemeral')); - loadTestFile(require.resolve('./event_log_alerts')); - loadTestFile(require.resolve('./snooze')); - loadTestFile(require.resolve('./bulk_edit')); - loadTestFile(require.resolve('./capped_action_type')); - loadTestFile(require.resolve('./scheduled_task_id')); - loadTestFile(require.resolve('./run_soon')); - loadTestFile(require.resolve('./flapping_history')); - loadTestFile(require.resolve('./check_registered_rule_types')); - loadTestFile(require.resolve('./alerts_as_data')); - // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 - - // note that this test will destroy existing spaces - loadTestFile(require.resolve('./migrations.ts')); - loadTestFile(require.resolve('./migrations/index.ts')); - }); -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/helpers.ts similarity index 73% rename from x-pack/test/alerting_api_integration/spaces_only/tests/index.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/helpers.ts index 890e6790f84c3..4baf887ecf939 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/helpers.ts @@ -8,15 +8,6 @@ import { FtrProviderContext } from '../../common/ftr_provider_context'; import { Spaces } from '../scenarios'; -// eslint-disable-next-line import/no-default-export -export default function alertingApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('alerting api integration spaces only', function () { - loadTestFile(require.resolve('./actions')); - loadTestFile(require.resolve('./alerting')); - loadTestFile(require.resolve('./action_task_params')); - }); -} - export async function buildUp(getService: FtrProviderContext['getService']) { const spacesService = getService('spaces'); for (const space of Object.values(Spaces)) { diff --git a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts index 3766d2a06364b..189f198db3812 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metric_threshold_alert.ts @@ -11,6 +11,7 @@ import { Aggregators, Comparator, CountMetricExpressionParams, + CustomMetricExpressionParams, NonCountMetricExpressionParams, } from '@kbn/infra-plugin/common/alerting/metrics'; import { InfraSource } from '@kbn/infra-plugin/common/source_configuration/source_configuration'; @@ -40,7 +41,7 @@ export default function ({ getService }: FtrProviderContext) { comparator: Comparator.GT_OR_EQ, aggType: Aggregators.SUM, metric: 'value', - }, + } as NonCountMetricExpressionParams, ], }; @@ -138,6 +139,73 @@ export default function ({ getService }: FtrProviderContext) { }, ]); }); + it('should alert with custom metric that is a document ratio', async () => { + const params = { + ...baseParams, + criteria: [ + { + timeSize: 5, + timeUnit: 'm', + threshold: [1], + comparator: Comparator.GT_OR_EQ, + aggType: Aggregators.CUSTOM, + customMetrics: [ + { name: 'A', aggType: 'count', filter: 'event.dataset: "apache2.error"' }, + { name: 'B', aggType: 'count' }, + ], + equation: '(A / B) * 100', + label: 'apache2 error ratio', + } as CustomMetricExpressionParams, + ], + }; + const config = { + ...configuration, + metricAlias: 'filebeat-*', + }; + const timeFrame = { end: DATES.ten_thousand_plus.max }; + const results = await evaluateRule( + esClient, + params, + config, + 10000, + true, + logger, + void 0, + timeFrame + ); + expect(results).to.eql([ + { + '*': { + timeSize: 5, + timeUnit: 'm', + threshold: [1], + comparator: '>=', + aggType: 'custom', + metric: 'apache2 error ratio', + label: 'apache2 error ratio', + customMetrics: [ + { name: 'A', aggType: 'count', filter: 'event.dataset: "apache2.error"' }, + { name: 'B', aggType: 'count' }, + ], + equation: '(A / B) * 100', + currentValue: 36.195262024407754, + timestamp: '2021-10-19T00:53:59.997Z', + shouldFire: true, + shouldWarn: false, + isNoData: false, + bucketKey: { groupBy0: '*' }, + context: { + cloud: undefined, + container: undefined, + host: undefined, + labels: undefined, + orchestrator: undefined, + tags: undefined, + }, + }, + }, + ]); + }); }); describe('with group by', () => { it('should trigger on document count', async () => { diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts index e5cd8babe8aff..84c555f751a53 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics_explorer.ts @@ -118,6 +118,52 @@ export default function ({ getService }: FtrProviderContext) { expect(firstSeries.rows).to.have.length(0); }); + it('should work for custom metrics', async () => { + const postBody = { + timerange: { + field: '@timestamp', + to: max, + from: min, + interval: '>=1m', + }, + indexPattern: 'metricbeat-*', + metrics: [ + { + aggregation: 'custom', + custom_metrics: [ + { name: 'A', aggregation: 'avg', field: 'system.cpu.user.pct' }, + { name: 'B', aggregation: 'avg', field: 'system.cpu.user.pct' }, + ], + equation: '(A + B) * 100', + }, + ], + }; + const response = await supertest + .post('/api/infra/metrics_explorer') + .set('kbn-xsrf', 'xxx') + .send(postBody) + .expect(200); + const body = decodeOrThrow(metricsExplorerResponseRT)(response.body); + expect(body.series).length(1); + const firstSeries = first(body.series) as any; + expect(firstSeries).to.have.property('id', '*'); + expect(firstSeries.columns).to.eql([ + { name: 'timestamp', type: 'date' }, + { name: 'metric_0', type: 'number' }, + ]); + expect(firstSeries.rows).to.have.length(8); + expect(firstSeries.rows).to.eql([ + { timestamp: 1547571300000, metric_0: 1.0666666666666667 }, + { timestamp: 1547571360000, metric_0: 0.4333333333333334 }, + { timestamp: 1547571420000, metric_0: 0.36666666666666664 }, + { timestamp: 1547571480000, metric_0: 0.30000000000000004 }, + { timestamp: 1547571540000, metric_0: 0.33333333333333337 }, + { timestamp: 1547571600000, metric_0: 0.26666666666666666 }, + { timestamp: 1547571660000, metric_0: 0.36666666666666664 }, + { timestamp: 1547571720000, metric_0: 0.36666666666666664 }, + ]); + }); + it('should work with groupBy', async () => { const postBody = { timerange: { diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 9fae13d374ddc..2f78112cb79fd 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -51,6 +51,7 @@ export default function ({ getService }: FtrProviderContext) { 'host_isolation_all', 'process_operations_all', 'file_operations_all', + 'execute_operations_all', ], uptime: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'], diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 9762ab2e343e1..304155224e87e 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -121,6 +121,7 @@ export default function ({ getService }: FtrProviderContext) { 'trusted_applications_all', 'trusted_applications_read', 'file_operations_all', + 'execute_operations_all', ], uptime: ['all', 'read', 'minimal_all', 'minimal_read'], securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'], diff --git a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts index c735bb130a78f..87d033be74743 100644 --- a/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts +++ b/x-pack/test/api_integration/apis/synthetics/delete_monitor.ts @@ -13,8 +13,7 @@ import { getFixtureJson } from '../uptime/rest/helper/get_fixture_json'; import { PrivateLocationTestService } from './services/private_location_test_service'; export default function ({ getService }: FtrProviderContext) { - // Failing: See https://github.com/elastic/kibana/issues/147990 - describe.skip('DeleteMonitorRoute', function () { + describe('DeleteMonitorRoute', function () { this.tags('skipCloud'); const supertest = getService('supertest'); diff --git a/x-pack/test/cases_api_integration/common/lib/attachments.ts b/x-pack/test/cases_api_integration/common/lib/attachments.ts new file mode 100644 index 0000000000000..129ede6fc21cc --- /dev/null +++ b/x-pack/test/cases_api_integration/common/lib/attachments.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 type SuperTest from 'supertest'; +import { + BulkGetAttachmentsResponse, + getCaseBulkGetAttachmentsUrl, +} from '@kbn/cases-plugin/common/api'; +import { User } from './authentication/types'; +import { superUser } from './authentication/users'; +import { getSpaceUrlPrefix } from './utils'; + +export const bulkGetAttachments = async ({ + supertest, + attachmentIds, + caseId, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + attachmentIds: string[]; + caseId: string; + auth?: { user: User; space: string | null }; + expectedHttpCode?: number; +}): Promise => { + const { body: comments } = await supertest + .post(`${getSpaceUrlPrefix(auth.space)}${getCaseBulkGetAttachmentsUrl(caseId)}`) + .send({ ids: attachmentIds }) + .set('kbn-xsrf', 'abc') + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return comments; +}; diff --git a/x-pack/test/cases_api_integration/common/lib/connectors.ts b/x-pack/test/cases_api_integration/common/lib/connectors.ts index 4948495ddf665..3d0d7b31bc7d1 100644 --- a/x-pack/test/cases_api_integration/common/lib/connectors.ts +++ b/x-pack/test/cases_api_integration/common/lib/connectors.ts @@ -9,10 +9,7 @@ import getPort from 'get-port'; import http from 'http'; import type SuperTest from 'supertest'; -import { - CASES_INTERNAL_URL, - CASE_CONFIGURE_CONNECTORS_URL, -} from '@kbn/cases-plugin/common/constants'; +import { CASE_CONFIGURE_CONNECTORS_URL } from '@kbn/cases-plugin/common/constants'; import { CasesConfigureResponse, CaseConnector, @@ -20,6 +17,7 @@ import { CasePostRequest, CaseResponse, GetCaseConnectorsResponse, + getCaseConnectorsUrl, } from '@kbn/cases-plugin/common/api'; import { ActionResult, FindActionResult } from '@kbn/actions-plugin/server/types'; import { User } from './authentication/types'; @@ -316,7 +314,7 @@ export const getConnectors = async ({ auth?: { user: User; space: string | null }; }): Promise => { const { body: connectors } = await supertest - .get(`${getSpaceUrlPrefix(auth.space)}${CASES_INTERNAL_URL}/${caseId}/_connectors`) + .get(`${getSpaceUrlPrefix(auth.space)}${getCaseConnectorsUrl(caseId)}`) .auth(auth.user.username, auth.user.password) .expect(expectedHttpCode); diff --git a/x-pack/test/cases_api_integration/common/lib/user_actions.ts b/x-pack/test/cases_api_integration/common/lib/user_actions.ts index 54e528b4336ba..d471fafca6b91 100644 --- a/x-pack/test/cases_api_integration/common/lib/user_actions.ts +++ b/x-pack/test/cases_api_integration/common/lib/user_actions.ts @@ -12,6 +12,8 @@ import { getCaseUserActionUrl, CaseUserActionDeprecatedResponse, CaseUserActionsDeprecatedResponse, + getCaseUserActionStatsUrl, + CaseUserActionStatsResponse, } from '@kbn/cases-plugin/common/api'; import type SuperTest from 'supertest'; import { User } from './authentication/types'; @@ -68,3 +70,22 @@ export const findCaseUserActions = async ({ return userActions; }; + +export const getCaseUserActionStats = async ({ + supertest, + caseID, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: SuperTest.SuperTest; + caseID: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): Promise => { + const { body: userActionStats } = await supertest + .get(`${getSpaceUrlPrefix(auth.space)}${getCaseUserActionStatsUrl(caseID)}`) + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return userActionStats; +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts index ecfbe3a369a95..7d57edba432e7 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/import_export.ts @@ -19,7 +19,6 @@ import { AttributesTypeUser, CommentsResponse, CaseAttributes, - CaseUserActionAttributes, CasePostRequest, PushedUserAction, ConnectorUserAction, @@ -27,7 +26,7 @@ import { CreateCaseUserAction, CaseStatuses, CaseSeverity, - CaseUserActionDeprecatedResponse, + CaseUserActionAttributesWithoutConnectorId, } from '@kbn/cases-plugin/common/api'; import { ESCaseSeverity, ESCaseStatus } from '@kbn/cases-plugin/server/services/cases/types'; import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; @@ -163,11 +162,15 @@ const expectImportToHaveOneCase = async (supertestService: supertest.SuperTest { +const expectImportToHaveCreateCaseUserAction = ( + userAction: CaseUserActionAttributesWithoutConnectorId +) => { expect(userAction.action).to.eql('create'); }; -const expectImportToHavePushUserAction = (userAction: CaseUserActionDeprecatedResponse) => { +const expectImportToHavePushUserAction = ( + userAction: CaseUserActionAttributesWithoutConnectorId +) => { const pushedUserAction = userAction as PushedUserAction; expect(userAction.action).to.eql('push_to_service'); expect(userAction.type).to.eql('pushed'); @@ -178,7 +181,9 @@ const expectImportToHavePushUserAction = (userAction: CaseUserActionDeprecatedRe ); }; -const expectImportToHaveUpdateConnector = (userAction: CaseUserActionDeprecatedResponse) => { +const expectImportToHaveUpdateConnector = ( + userAction: CaseUserActionAttributesWithoutConnectorId +) => { const connectorUserAction = userAction as ConnectorUserAction; expect(userAction.action).to.eql('update'); expect(userAction.type).to.eql('connector'); @@ -213,7 +218,7 @@ const expectExportToHaveCaseSavedObject = ( }; const expectExportToHaveUserActions = (objects: SavedObject[], caseRequest: CasePostRequest) => { - const userActionSOs = findSavedObjectsByType( + const userActionSOs = findSavedObjectsByType( objects, CASE_USER_ACTION_SAVED_OBJECT ); @@ -225,7 +230,7 @@ const expectExportToHaveUserActions = (objects: SavedObject[], caseRequest: Case }; const expectCaseCreateUserAction = ( - userActions: Array>, + userActions: Array>, caseRequest: CasePostRequest ) => { const userActionForCaseCreate = findUserActionSavedObject(userActions, 'create', 'create_case'); @@ -252,7 +257,7 @@ const expectCaseCreateUserAction = ( }; const expectCreateCommentUserAction = ( - userActions: Array> + userActions: Array> ) => { const userActionForComment = findUserActionSavedObject(userActions, 'create', 'comment'); const createCommentUserAction = userActionForComment!.attributes as CommentUserAction; @@ -280,9 +285,9 @@ const findSavedObjectsByType = ( }; const findUserActionSavedObject = ( - savedObjects: Array>, + savedObjects: Array>, action: string, type: string -): SavedObject | undefined => { +): SavedObject | undefined => { return savedObjects.find((so) => so.attributes.action === action && so.attributes.type === type); }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts index 177e516af7e28..107371491ff38 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/migrations.ts @@ -29,11 +29,16 @@ export default function createGetTests({ getService }: FtrProviderContext) { // tests upgrading a 7.10.0 saved object to the latest version describe('7.10.0 -> latest stack version', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json' + ); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json' + ); + await deleteAllCaseItems(es); }); it('migrates cases connector', async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts index 59c86d0437768..9cbefbd2ee03d 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/comments/migrations.ts @@ -21,11 +21,16 @@ export default function createGetTests({ getService }: FtrProviderContext) { describe('migrations', () => { describe('7.11.0', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json' + ); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json' + ); + await deleteAllCaseItems(es); }); it('7.11.0 migrates cases comments', async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/migrations.ts index 308a22c371a48..6f489d99b52f3 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/migrations.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { CASE_CONFIGURE_URL, SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common/constants'; +import { SECURITY_SOLUTION_OWNER } from '@kbn/cases-plugin/common/constants'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { getConfiguration, @@ -21,34 +21,6 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('es'); describe('migrations', () => { - describe('7.10.0', () => { - before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); - }); - - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); - }); - - it('7.10.0 migrates configure cases connector', async () => { - const { body } = await supertest - .get(`${CASE_CONFIGURE_URL}`) - .set('kbn-xsrf', 'true') - .send() - .expect(200); - - expect(body.length).to.be(1); - expect(body[0]).key('connector'); - expect(body[0]).not.key('connector_id'); - expect(body[0].connector).to.eql({ - id: 'connector-1', - name: 'Connector 1', - type: '.none', - fields: null, - }); - }); - }); - describe('7.13.2', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/7.13.2'); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts index 311c245c11984..b4392c95d5351 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/index.ts @@ -31,6 +31,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./cases/tags/get_tags')); loadTestFile(require.resolve('./user_actions/get_all_user_actions')); loadTestFile(require.resolve('./user_actions/find_user_actions')); + loadTestFile(require.resolve('./user_actions/get_user_action_stats')); loadTestFile(require.resolve('./configure/get_configure')); loadTestFile(require.resolve('./configure/patch_configure')); loadTestFile(require.resolve('./configure/post_configure')); @@ -46,6 +47,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./internal/bulk_create_attachments')); loadTestFile(require.resolve('./internal/bulk_get_cases')); + loadTestFile(require.resolve('./internal/bulk_get_attachments')); loadTestFile(require.resolve('./internal/get_connectors')); /** diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts new file mode 100644 index 0000000000000..2f15c221c3ace --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/bulk_get_attachments.ts @@ -0,0 +1,459 @@ +/* + * 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 { + AttributesTypeExternalReference, + AttributesTypeExternalReferenceSO, + CaseResponse, + CommentResponseTypePersistableState, +} from '@kbn/cases-plugin/common/api'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +import { + postCaseReq, + getPostCaseRequest, + postCommentUserReq, + postCommentAlertReq, + persistableStateAttachment, + postExternalReferenceSOReq, + postExternalReferenceESReq, +} from '../../../../common/lib/mock'; +import { + deleteAllCaseItems, + createCase, + createComment, + bulkCreateAttachments, + ensureSavedObjectIsAuthorized, +} from '../../../../common/lib/utils'; +import { bulkGetAttachments } from '../../../../common/lib/attachments'; +import { + globalRead, + noKibanaPrivileges, + obsOnly, + obsOnlyRead, + obsSec, + obsSecRead, + secOnly, + secOnlyRead, + superUser, +} from '../../../../common/lib/authentication/users'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('bulk_get_attachments', () => { + describe('setup using two comments', () => { + let updatedCase: CaseResponse; + + before(async () => { + const postedCase = await createCase(supertest, postCaseReq); + updatedCase = await bulkCreateAttachments({ + caseId: postedCase.id, + params: [postCommentUserReq, postCommentAlertReq], + supertest, + }); + }); + + after(async () => { + await deleteAllCaseItems(es); + }); + + it('should retrieve a single attachment', async () => { + const response = await bulkGetAttachments({ + attachmentIds: [updatedCase.comments![0].id], + caseId: updatedCase.id, + supertest, + }); + + expect(response.attachments.length).to.be(1); + expect(response.errors.length).to.be(0); + expect(response.attachments[0].id).to.eql(updatedCase.comments![0].id); + }); + + it('should retrieve a multiple attachments', async () => { + const response = await bulkGetAttachments({ + attachmentIds: [updatedCase.comments![0].id, updatedCase.comments![1].id], + caseId: updatedCase.id, + supertest, + }); + + expect(response.attachments.length).to.be(2); + expect(response.errors.length).to.be(0); + expect(response.attachments[0].id).to.eql(updatedCase.comments![0].id); + expect(response.attachments[1].id).to.eql(updatedCase.comments![1].id); + }); + + it('returns an empty array when no ids are requested', async () => { + const { attachments, errors } = await bulkGetAttachments({ + attachmentIds: [], + caseId: updatedCase.id, + supertest, + expectedHttpCode: 200, + }); + + expect(attachments.length).to.be(0); + expect(errors.length).to.be(0); + }); + + it('returns a 400 when more than 10k ids are requested', async () => { + await bulkGetAttachments({ + attachmentIds: Array.from(Array(10001).keys()).map((item) => item.toString()), + caseId: updatedCase.id, + supertest, + expectedHttpCode: 400, + }); + }); + + it('populates the errors field with attachments that could not be found', async () => { + const response = await bulkGetAttachments({ + attachmentIds: [updatedCase.comments![0].id, 'does-not-exist'], + caseId: updatedCase.id, + supertest, + expectedHttpCode: 200, + }); + + expect(response.attachments.length).to.be(1); + expect(response.errors.length).to.be(1); + expect(response.errors[0]).to.eql({ + error: 'Not Found', + message: 'Saved object [cases-comments/does-not-exist] not found', + status: 404, + attachmentId: 'does-not-exist', + }); + }); + }); + + describe('inject references into attributes', () => { + it('should inject the persistable state attachment references into the attributes', async () => { + const postedCase = await createCase(supertest, postCaseReq); + const patchedCase = await createComment({ + supertest, + caseId: postedCase.id, + params: persistableStateAttachment, + }); + + const response = await bulkGetAttachments({ + attachmentIds: [patchedCase.comments![0].id], + caseId: patchedCase.id, + supertest, + }); + + const persistableState = response.attachments[0] as CommentResponseTypePersistableState; + + expect(persistableState.persistableStateAttachmentState).to.eql( + persistableStateAttachment.persistableStateAttachmentState + ); + }); + + it("should inject saved object external reference style attachment's references into the attributes", async () => { + const postedCase = await createCase(supertest, postCaseReq); + const patchedCase = await createComment({ + supertest, + caseId: postedCase.id, + params: postExternalReferenceSOReq, + }); + + const response = await bulkGetAttachments({ + attachmentIds: [patchedCase.comments![0].id], + caseId: patchedCase.id, + supertest, + }); + + const externalRefSO = response.attachments[0] as AttributesTypeExternalReferenceSO; + + expect(externalRefSO.externalReferenceId).to.eql( + postExternalReferenceSOReq.externalReferenceId + ); + expect(externalRefSO.externalReferenceStorage.soType).to.eql( + postExternalReferenceSOReq.externalReferenceStorage.soType + ); + expect(externalRefSO.externalReferenceStorage.type).to.eql( + postExternalReferenceSOReq.externalReferenceStorage.type + ); + }); + + it("should inject the elasticsearch external reference style attachment's references into the attributes", async () => { + const postedCase = await createCase(supertest, postCaseReq); + const patchedCase = await createComment({ + supertest, + caseId: postedCase.id, + params: postExternalReferenceESReq, + }); + + const response = await bulkGetAttachments({ + attachmentIds: [patchedCase.comments![0].id], + caseId: patchedCase.id, + supertest, + }); + + const externalRefES = response.attachments[0] as AttributesTypeExternalReference; + + expect(externalRefES.externalReferenceId).to.eql( + postExternalReferenceESReq.externalReferenceId + ); + expect(externalRefES.externalReferenceStorage.type).to.eql( + postExternalReferenceESReq.externalReferenceStorage.type + ); + }); + }); + + describe('rbac', () => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + describe('security and observability cases', () => { + let secCase: CaseResponse; + let obsCase: CaseResponse; + let secAttachmentId: string; + let obsAttachmentId: string; + + beforeEach(async () => { + [secCase, obsCase] = await Promise.all([ + // Create case owned by the security solution user + createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolutionFixture' }), + 200, + { + user: superUser, + space: 'space1', + } + ), + // Create case owned by the observability user + createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'observabilityFixture' }), + 200, + { + user: superUser, + space: 'space1', + } + ), + ]); + + [secCase, obsCase] = await Promise.all([ + createComment({ + supertest: supertestWithoutAuth, + caseId: secCase.id, + params: postCommentUserReq, + auth: { user: superUser, space: 'space1' }, + }), + createComment({ + supertest: supertestWithoutAuth, + caseId: obsCase.id, + params: { ...postCommentUserReq, owner: 'observabilityFixture' }, + auth: { user: superUser, space: 'space1' }, + }), + ]); + + secAttachmentId = secCase.comments![0].id; + obsAttachmentId = obsCase.comments![0].id; + }); + + it('should be able to read attachments', async () => { + for (const scenario of [ + { + user: globalRead, + owners: ['securitySolutionFixture'], + caseId: secCase.id, + attachmentIds: [secAttachmentId], + }, + { + user: globalRead, + owners: ['observabilityFixture'], + caseId: obsCase.id, + attachmentIds: [obsAttachmentId], + }, + { + user: superUser, + owners: ['securitySolutionFixture'], + caseId: secCase.id, + attachmentIds: [secAttachmentId], + }, + { + user: superUser, + owners: ['observabilityFixture'], + caseId: obsCase.id, + attachmentIds: [obsAttachmentId], + }, + { + user: secOnlyRead, + owners: ['securitySolutionFixture'], + caseId: secCase.id, + attachmentIds: [secAttachmentId], + }, + { + user: obsOnlyRead, + owners: ['observabilityFixture'], + caseId: obsCase.id, + attachmentIds: [obsAttachmentId], + }, + { + user: obsSecRead, + owners: ['securitySolutionFixture'], + caseId: secCase.id, + attachmentIds: [secAttachmentId], + }, + { + user: obsSecRead, + owners: ['observabilityFixture'], + caseId: obsCase.id, + attachmentIds: [obsAttachmentId], + }, + { + user: obsSec, + owners: ['securitySolutionFixture'], + caseId: secCase.id, + attachmentIds: [secAttachmentId], + }, + { + user: obsSec, + owners: ['observabilityFixture'], + caseId: obsCase.id, + attachmentIds: [obsAttachmentId], + }, + { + user: secOnly, + owners: ['securitySolutionFixture'], + caseId: secCase.id, + attachmentIds: [secAttachmentId], + }, + { + user: obsOnly, + owners: ['observabilityFixture'], + caseId: obsCase.id, + attachmentIds: [obsAttachmentId], + }, + ]) { + const { attachments, errors } = await bulkGetAttachments({ + supertest: supertestWithoutAuth, + attachmentIds: scenario.attachmentIds, + caseId: scenario.caseId, + auth: { user: scenario.user, space: 'space1' }, + }); + + const numberOfCasesThatShouldBeReturned = 1; + ensureSavedObjectIsAuthorized( + attachments, + numberOfCasesThatShouldBeReturned, + scenario.owners + ); + + expect(errors.length).to.be(0); + } + }); + + it('should return an association error when an observability attachment is requested for a security case', async () => { + const { attachments, errors } = await bulkGetAttachments({ + supertest: supertestWithoutAuth, + attachmentIds: [secAttachmentId, obsAttachmentId], + caseId: secCase.id, + auth: { user: secOnlyRead, space: 'space1' }, + }); + + expect(attachments.length).to.be(1); + expect(attachments[0].owner).to.be('securitySolutionFixture'); + expect(errors.length).to.be(1); + expect(errors[0]).to.eql({ + attachmentId: obsAttachmentId, + error: 'Bad Request', + message: `Attachment is not attached to case id=${secCase.id}`, + status: 400, + }); + }); + + it('should return an authorization error when a security solution user is attempting to retrieve an observability attachment', async () => { + await bulkGetAttachments({ + supertest: supertestWithoutAuth, + attachmentIds: [obsAttachmentId], + caseId: obsCase.id, + auth: { user: secOnlyRead, space: 'space1' }, + expectedHttpCode: 403, + }); + }); + + it('should return authorization error when a observability user requests a security attachment for a security case', async () => { + await bulkGetAttachments({ + supertest: supertestWithoutAuth, + attachmentIds: [secAttachmentId], + caseId: secCase.id, + auth: { user: obsOnlyRead, space: 'space1' }, + expectedHttpCode: 403, + }); + }); + + it('should return an error for the observability attachment that is not associated to the case, and an error for an unknown attachment', async () => { + const { attachments, errors } = await bulkGetAttachments({ + supertest: supertestWithoutAuth, + attachmentIds: [obsAttachmentId, 'does-not-exist'], + caseId: secCase.id, + auth: { user: secOnly, space: 'space1' }, + }); + + expect(attachments.length).to.be(0); + expect(errors.length).to.be(2); + expect(errors[0]).to.eql({ + error: 'Not Found', + message: 'Saved object [cases-comments/does-not-exist] not found', + status: 404, + attachmentId: 'does-not-exist', + }); + expect(errors[1]).to.eql({ + attachmentId: obsAttachmentId, + error: 'Bad Request', + message: `Attachment is not attached to case id=${secCase.id}`, + status: 400, + }); + }); + }); + + for (const scenario of [ + { user: noKibanaPrivileges, space: 'space1' }, + { user: secOnly, space: 'space2' }, + ]) { + it(`User ${scenario.user.username} with role(s) ${scenario.user.roles.join()} and space ${ + scenario.space + } - should not bulk get attachments`, async () => { + // super user creates a case at the appropriate space + const newCase = await createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'securitySolutionFixture' }), + 200, + { + user: superUser, + space: scenario.space, + } + ); + + const patchedCase = await createComment({ + supertest: supertestWithoutAuth, + caseId: newCase.id, + params: postCommentUserReq, + auth: { user: superUser, space: scenario.space }, + }); + + await bulkGetAttachments({ + supertest: supertestWithoutAuth, + attachmentIds: [patchedCase.comments![0].id], + caseId: newCase.id, + auth: { + user: scenario.user, + space: scenario.space, + }, + expectedHttpCode: 403, + }); + }); + } + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_user_action_stats.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_user_action_stats.ts new file mode 100644 index 0000000000000..0f004820ad52b --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/get_user_action_stats.ts @@ -0,0 +1,206 @@ +/* + * 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 { + CaseResponse, + CaseSeverity, + CaseStatuses, + ConnectorTypes, +} from '@kbn/cases-plugin/common/api'; + +import { + globalRead, + obsSec, + obsSecRead, + noKibanaPrivileges, + secOnly, + secOnlyRead, + superUser, + obsOnly, + obsOnlyRead, +} from '../../../../common/lib/authentication/users'; +import { getCaseUserActionStats } from '../../../../common/lib/user_actions'; +import { + getPostCaseRequest, + persistableStateAttachment, + postCaseReq, + postCommentActionsReq, + postCommentAlertReq, + postCommentUserReq, + postExternalReferenceESReq, +} from '../../../../common/lib/mock'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { + bulkCreateAttachments, + createCase, + createComment, + deleteAllCaseItems, + updateCase, + superUserSpace1Auth, +} from '../../../../common/lib/utils'; + +const getCaseUpdateData = (id: string, version: string) => ({ + status: CaseStatuses.open, + severity: CaseSeverity.MEDIUM, + title: 'new title', + description: 'new desc', + settings: { + syncAlerts: false, + }, + tags: ['one', 'two'], + connector: { + id: 'my-id', + name: 'Jira', + type: ConnectorTypes.jira as const, + fields: null, + }, + id, + version, +}); + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('get_user_action_stats', () => { + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('returns correct total for comments', async () => { + // 1 creation action + const theCase = await createCase(supertest, postCaseReq); + + await bulkCreateAttachments({ + supertest, + caseId: theCase.id, + params: [ + // Only this one should show up in total_comments + postCommentUserReq, + // The ones below count as total_other_actions + postExternalReferenceESReq, + persistableStateAttachment, + postCommentActionsReq, + postCommentAlertReq, + ], + }); + + const userActionTotals = await getCaseUserActionStats({ supertest, caseID: theCase.id }); + + expect(userActionTotals.total).to.equal(6); + expect(userActionTotals.total_comments).to.equal(1); + expect(userActionTotals.total_other_actions).to.equal(5); + expect(userActionTotals.total).to.equal( + userActionTotals.total_comments + userActionTotals.total_other_actions + ); + }); + + it('returns the correct stats when a case update occurs', async () => { + // 1 creation action + const theCase = await createCase(supertest, postCaseReq); + + // this update should account for 7 "other actions" + await updateCase({ + supertest, + params: { + cases: [getCaseUpdateData(theCase.id, theCase.version)], + }, + }); + + await bulkCreateAttachments({ + supertest, + caseId: theCase.id, + params: [ + // only this one should show up in total_comments + postCommentUserReq, + // the ones below count as total_other_actions + postExternalReferenceESReq, + persistableStateAttachment, + postCommentActionsReq, + postCommentAlertReq, + ], + }); + + const userActionTotals = await getCaseUserActionStats({ supertest, caseID: theCase.id }); + + expect(userActionTotals.total).to.equal(13); + expect(userActionTotals.total_comments).to.equal(1); + expect(userActionTotals.total_other_actions).to.equal(12); + expect(userActionTotals.total).to.equal( + userActionTotals.total_comments + userActionTotals.total_other_actions + ); + }); + + describe('rbac', () => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + let theCase: CaseResponse; + beforeEach(async () => { + theCase = await createCase(supertestWithoutAuth, getPostCaseRequest(), 200, { + user: superUser, + space: 'space1', + }); + + await updateCase({ + supertest: supertestWithoutAuth, + params: { + cases: [getCaseUpdateData(theCase.id, theCase.version)], + }, + auth: superUserSpace1Auth, + }); + + await createComment({ + supertest, + caseId: theCase.id, + params: postCommentUserReq, + auth: superUserSpace1Auth, + }); + }); + + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should get the user actions for a case when the user has the correct permissions', async () => { + for (const user of [globalRead, superUser, secOnly, secOnlyRead, obsSec, obsSecRead]) { + const userActionTotals = await getCaseUserActionStats({ + supertest: supertestWithoutAuth, + caseID: theCase.id, + auth: { user, space: 'space1' }, + }); + + expect(userActionTotals.total).to.equal(9); + expect(userActionTotals.total_comments).to.equal(1); + expect(userActionTotals.total_other_actions).to.equal(8); + expect(userActionTotals.total).to.equal( + userActionTotals.total_comments + userActionTotals.total_other_actions + ); + } + }); + + for (const scenario of [ + { user: noKibanaPrivileges, space: 'space1' }, + { user: secOnly, space: 'space2' }, + { user: obsOnly, space: 'space1' }, + { user: obsOnlyRead, space: 'space1' }, + ]) { + it(`should 403 when requesting the user action stats of a case with user ${ + scenario.user.username + } with role(s) ${scenario.user.roles.join()} and space ${scenario.space}`, async () => { + await getCaseUserActionStats({ + supertest: supertestWithoutAuth, + caseID: theCase.id, + auth: { user: scenario.user, space: scenario.space }, + expectedHttpCode: 403, + }); + }); + } + }); + }); +}; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts index 90d8f3986648e..5d80154752710 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/user_actions/migrations.ts @@ -28,11 +28,16 @@ export default function createGetTests({ getService }: FtrProviderContext) { const CASE_ID = 'e1900ac0-017f-11eb-93f8-d161651bf509'; before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json' + ); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/cases/migrations/7.10.0'); + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json' + ); + await deleteAllCaseItems(es); }); it('7.10.0 migrates user actions connector', async () => { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts index 4a3a2f876f843..a167b8ded642a 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/index.ts @@ -38,6 +38,7 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => { loadTestFile(require.resolve('./user_profiles/get_current')); // Internal routes + loadTestFile(require.resolve('./internal/get_user_action_stats')); loadTestFile(require.resolve('./internal/suggest_user_profiles')); loadTestFile(require.resolve('./internal/get_connectors')); diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_connectors.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_connectors.ts index 3d1b59a2018ab..83be12a31a432 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_connectors.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_connectors.ts @@ -8,7 +8,12 @@ import http from 'http'; import expect from '@kbn/expect'; -import { ActionTypes, CaseSeverity, ConnectorTypes } from '@kbn/cases-plugin/common/api'; +import { + ActionTypes, + CaseSeverity, + CaseStatuses, + ConnectorTypes, +} from '@kbn/cases-plugin/common/api'; import { globalRead, noKibanaPrivileges, @@ -246,7 +251,7 @@ export default ({ getService }: FtrProviderContext): void => { connectorId: connector.id, }); - const pachedCase = await createComment({ + const patched = await createComment({ supertest, caseId: postedCase.id, params: postCommentUserReq, @@ -269,8 +274,8 @@ export default ({ getService }: FtrProviderContext): void => { params: { cases: [ { - id: pachedCase.id, - version: pachedCase.version, + id: patched.id, + version: patched.version, connector: { id: serviceNow2.id, name: 'ServiceNow 2 Connector', @@ -290,7 +295,7 @@ export default ({ getService }: FtrProviderContext): void => { await pushCase({ supertest, - caseId: pachedCase.id, + caseId: patched.id, connectorId: serviceNow2.id, }); @@ -303,23 +308,26 @@ export default ({ getService }: FtrProviderContext): void => { const latestPush = pushes[pushes.length - 1]; expect(Object.keys(connectors).length).to.be(2); - expect(connectors[serviceNow2.id].push.latestUserActionPushDate).to.eql( + expect(connectors[serviceNow2.id].push.details?.latestUserActionPushDate).to.eql( latestPush.created_at ); - expect(connectors[serviceNow2.id].push.externalService?.connector_id).to.eql( + expect(connectors[serviceNow2.id].push.details?.externalService?.connector_id).to.eql( serviceNow2.id ); - expect(connectors[serviceNow2.id].push.externalService?.connector_name).to.eql( + expect(connectors[serviceNow2.id].push.details?.externalService?.connector_name).to.eql( serviceNow2.name ); - expect(connectors[serviceNow2.id].push.externalService?.connector_name).to.not.eql( - connector.name + expect( + connectors[serviceNow2.id].push.details?.externalService?.connector_name + ).to.not.eql(connector.name); + expect(connectors[serviceNow2.id].push.details?.externalService?.connector_id).to.not.eql( + connector.id ); }); }); - describe('latestPushDate', () => { - it('does not set latestPushDate or oldestPushDate when the connector has not been used to push', async () => { + describe('latestUserActionPushDate', () => { + it('does not set latestUserActionPushDate or oldestPushDate when the connector has not been used to push', async () => { const { postedCase, connector } = await createCaseWithConnector({ supertest, serviceNowSimulatorURL, @@ -330,11 +338,11 @@ export default ({ getService }: FtrProviderContext): void => { expect(Object.keys(connectors).length).to.be(1); expect(connectors).to.have.property(connector.id); - expect(connectors[connector.id].push.latestUserActionPushDate).to.be(undefined); - expect(connectors[connector.id].push.oldestUserActionPushDate).to.be(undefined); + expect(connectors[connector.id].push.details?.latestUserActionPushDate).to.be(undefined); + expect(connectors[connector.id].push.details?.oldestUserActionPushDate).to.be(undefined); }); - it('sets latestPushDate to the most recent push date and oldestPushDate to the first push date', async () => { + it('sets latestUserActionPushDate to the most recent push date and oldestPushDate to the first push date', async () => { const { postedCase, connector } = await createCaseWithConnector({ supertest, serviceNowSimulatorURL, @@ -369,10 +377,10 @@ export default ({ getService }: FtrProviderContext): void => { const latestPush = pushes[pushes.length - 1]; expect(Object.keys(connectors).length).to.be(1); - expect(connectors[connector.id].push.latestUserActionPushDate).to.eql( + expect(connectors[connector.id].push.details?.latestUserActionPushDate).to.eql( latestPush.created_at ); - expect(connectors[connector.id].push.oldestUserActionPushDate).to.eql( + expect(connectors[connector.id].push.details?.oldestUserActionPushDate).to.eql( oldestPush.created_at ); }); @@ -506,6 +514,39 @@ export default ({ getService }: FtrProviderContext): void => { expect(connectors[connector.id].push.needsToBePushed).to.be(false); }); + it('sets needs to push to false when the status of a case was changed after the last push', async () => { + const { postedCase, connector } = await createCaseWithConnector({ + supertest, + serviceNowSimulatorURL, + actionsRemover, + }); + + const pushedCase = await pushCase({ + supertest, + caseId: postedCase.id, + connectorId: connector.id, + }); + + await updateCase({ + supertest, + params: { + cases: [ + { + id: pushedCase.id, + version: pushedCase.version, + status: CaseStatuses['in-progress'], + }, + ], + }, + }); + + const connectors = await getConnectors({ caseId: postedCase.id, supertest }); + + expect(Object.keys(connectors).length).to.be(1); + expect(connectors[connector.id].id).to.be(connector.id); + expect(connectors[connector.id].push.needsToBePushed).to.be(false); + }); + it('sets needs to push to false the service now connector and true for jira', async () => { const { postedCase, connector: serviceNowConnector } = await createCaseWithConnector({ supertest, diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_user_action_stats.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_user_action_stats.ts new file mode 100644 index 0000000000000..370cd2a98149b --- /dev/null +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/internal/get_user_action_stats.ts @@ -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 http from 'http'; +import expect from '@kbn/expect'; + +import { ConnectorTypes } from '@kbn/cases-plugin/common/api'; +import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { createCase, deleteAllCaseItems, pushCase, updateCase } from '../../../../common/lib/utils'; +import { postCaseReq } from '../../../../common/lib/mock'; +import { + createCaseWithConnector, + createConnector, + getJiraConnector, + getServiceNowSimulationServer, +} from '../../../../common/lib/connectors'; +import { getCaseUserActionStats } from '../../../../common/lib/user_actions'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + const actionsRemover = new ActionsRemover(supertest); + + describe('get_case_user_action_stats', () => { + let serviceNowSimulatorURL: string = ''; + let serviceNowServer: http.Server; + + before(async () => { + const { server, url } = await getServiceNowSimulationServer(); + serviceNowServer = server; + serviceNowSimulatorURL = url; + }); + + afterEach(async () => { + await deleteAllCaseItems(es); + await actionsRemover.removeAll(); + }); + + after(async () => { + serviceNowServer.close(); + }); + + it('connectors are counted in total_other_actions', async () => { + const [{ postedCase, connector: serviceNowConnector }, jiraConnector] = await Promise.all([ + createCaseWithConnector({ + supertest, + serviceNowSimulatorURL, + actionsRemover, + }), + createConnector({ + supertest, + req: { + ...getJiraConnector(), + }, + }), + ]); + + actionsRemover.add('default', jiraConnector.id, 'action', 'actions'); + + const theCase = await pushCase({ + supertest, + caseId: postedCase.id, + connectorId: serviceNowConnector.id, + }); + + await updateCase({ + supertest, + params: { + cases: [ + { + id: theCase.id, + version: theCase.version, + connector: { + id: jiraConnector.id, + name: 'Jira', + type: ConnectorTypes.jira, + fields: { issueType: 'Task', priority: null, parent: null }, + }, + }, + ], + }, + }); + + const userActionTotals = await getCaseUserActionStats({ supertest, caseID: theCase.id }); + + expect(userActionTotals.total).to.equal(3); + expect(userActionTotals.total_comments).to.equal(0); + expect(userActionTotals.total_other_actions).to.equal(3); + expect(userActionTotals.total).to.equal( + userActionTotals.total_comments + userActionTotals.total_other_actions + ); + }); + + it('assignees are counted in total_other_actions', async () => { + // 1 creation action + const theCase = await createCase(supertest, postCaseReq); + + // 1 assignee action + await updateCase({ + supertest, + params: { + cases: [ + { + assignees: [ + { + uid: '123', + }, + ], + id: theCase.id, + version: theCase.version, + }, + ], + }, + }); + + const userActionTotals = await getCaseUserActionStats({ supertest, caseID: theCase.id }); + + expect(userActionTotals.total).to.equal(2); + expect(userActionTotals.total_comments).to.equal(0); + expect(userActionTotals.total_other_actions).to.equal(2); + expect(userActionTotals.total).to.equal( + userActionTotals.total_comments + userActionTotals.total_other_actions + ); + }); + }); +}; diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index 405efd73c2ce7..05a2f25cfc4e2 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -17,7 +17,8 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); let elasticAgentpkgVersion: string; - describe('fleet_list_agent', () => { + // FLAKY: https://github.com/elastic/kibana/issues/149937 + describe.skip('fleet_list_agent', () => { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/fleet/agents'); const getPkRes = await supertest @@ -197,5 +198,25 @@ export default function ({ getService }: FtrProviderContext) { expect(agent2.metrics?.memory_size_byte_avg).equal(undefined); expect(agent2.metrics?.cpu_avg).equal(undefined); }); + + it('should return a status summary if getStatusSummary provided', async () => { + const { body: apiResponse } = await supertest + .get('/api/fleet/agents?getStatusSummary=true&perPage=0') + .expect(200); + + expect(apiResponse.items).to.eql([]); + + expect(apiResponse.statusSummary).to.eql({ + degraded: 0, + enrolling: 0, + error: 0, + inactive: 0, + offline: 4, + online: 0, + unenrolled: 0, + unenrolling: 0, + updating: 0, + }); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index 89bf920c8ca2f..0c31fd5d710cc 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -1027,7 +1027,6 @@ export default function (providerContext: FtrProviderContext) { .get(`/api/fleet/agents/action_status`) .set('kbn-xsrf', 'xxx'); const actionStatus = body.items[0]; - expect(actionStatus.status).to.eql('FAILED'); expect(actionStatus.nbAgentsFailed).to.eql(1); }); diff --git a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts index 45d20b92c1e2b..7cc75446036e9 100644 --- a/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/group1/feature_controls/dashboard_security.ts @@ -454,8 +454,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/116881 - describe.skip('no dashboard privileges', () => { + describe('no dashboard privileges', () => { before(async () => { await security.role.create('no_dashboard_privileges_role', { elasticsearch: { diff --git a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts index eac81107807b2..612f6c44c2c27 100644 --- a/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts +++ b/x-pack/test/functional/apps/dashboard/group2/sync_colors.ts @@ -54,7 +54,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.dashboard.clickCreateDashboardPrompt(); await dashboardAddPanel.clickCreateNewLink(); - await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.goToTimeRange(); await PageObjects.lens.configureDimension({ @@ -72,7 +71,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.lens.save('vis1', false, true); await PageObjects.header.waitUntilLoadingHasFinished(); await dashboardAddPanel.clickCreateNewLink(); - await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', diff --git a/x-pack/test/functional/apps/lens/group2/share.ts b/x-pack/test/functional/apps/lens/group2/share.ts index e50eb20ee7c3c..c7cd22974b289 100644 --- a/x-pack/test/functional/apps/lens/group2/share.ts +++ b/x-pack/test/functional/apps/lens/group2/share.ts @@ -14,8 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const filterBarService = getService('filterBar'); const queryBar = getService('queryBar'); - // Failing: See https://github.com/elastic/kibana/issues/149163 - describe.skip('lens share tests', () => { + describe('lens share tests', () => { before(async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); }); @@ -73,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.navigateTo(url); // check that it's the same configuration in the new URL when ready - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization('xyVisChart'); expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( 'Average of bytes' ); @@ -93,7 +92,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await filterBarService.addFilter({ field: 'bytes', operation: 'is', value: '1' }); await queryBar.setQuery('host.keyword www.elastic.co'); await queryBar.submitQuery(); - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization('xyVisChart'); const url = await PageObjects.lens.getUrl('snapshot'); await browser.openNewTab(); @@ -102,7 +101,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.navigateTo(url); // check that it's the same configuration in the new URL when ready - await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.waitForVisualization('xyVisChart'); expect(await filterBarService.getFiltersLabel()).to.eql(['bytes: 1']); expect(await queryBar.getQueryString()).to.be('host.keyword www.elastic.co'); await browser.closeCurrentWindow(); diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/config.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/config.ts new file mode 100644 index 0000000000000..40617c64ba398 --- /dev/null +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + reportName: 'Chrome X-Pack UI Functional Tests - transform - creation - index pattern', + }, + }; +} diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts similarity index 96% rename from x-pack/test/functional/apps/transform/creation_index_pattern.ts rename to x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts index 5c240b2c0403c..1cb93ad03efe3 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/creation_index_pattern.ts @@ -7,20 +7,20 @@ import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; import { GroupByEntry, isLatestTransformTestData, isPivotTransformTestData, LatestTransformTestData, PivotTransformTestData, -} from '.'; +} from '../../helpers'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const canvasElement = getService('canvasElement'); const esArchiver = getService('esArchiver'); const transform = getService('transform'); - const PageObjects = getPageObjects(['discover']); + const pageObjects = getPageObjects(['discover']); describe('creation_index_pattern', function () { before(async () => { @@ -486,6 +486,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await transform.testExecution.logTestStep('has correct transform function selected'); await transform.wizard.assertSelectedTransformFunction('pivot'); + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); + + await transform.testExecution.logTestStep('displays an empty index preview'); + await transform.wizard.assertIndexPreviewEmpty(); + + await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`); + await transform.datePicker.quickSelect(); + await transform.testExecution.logTestStep('loads the index preview'); await transform.wizard.assertIndexPreviewLoaded(); @@ -699,16 +710,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await transform.testExecution.logTestStep('should navigate to discover'); await transform.table.clickTransformRowAction(testData.transformId, 'Discover'); - await PageObjects.discover.waitUntilSearchingHasFinished(); + await pageObjects.discover.waitUntilSearchingHasFinished(); if (testData.discoverAdjustSuperDatePicker) { + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); await transform.discover.assertNoResults(testData.destinationIndex); await transform.testExecution.logTestStep( 'should switch quick select lookback to years' ); - await transform.discover.assertSuperDatePickerToggleQuickMenuButtonExists(); - await transform.discover.openSuperDatePicker(); - await transform.discover.quickSelectYears(); + await transform.datePicker.quickSelect(); } await transform.discover.assertDiscoverQueryHits(testData.expected.discoverQueryHits); diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts new file mode 100644 index 0000000000000..9e09c4e1c51fa --- /dev/null +++ b/x-pack/test/functional/apps/transform/creation/index_pattern/index.ts @@ -0,0 +1,37 @@ +/* + * 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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + describe('transform - creation - index pattern', function () { + this.tags('transform'); + + before(async () => { + await transform.securityCommon.createTransformRoles(); + await transform.securityCommon.createTransformUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await transform.securityUI.logout(); + + await transform.securityCommon.cleanTransformUsers(); + await transform.securityCommon.cleanTransformRoles(); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); + + await transform.testResources.resetKibanaTimeZone(); + }); + + loadTestFile(require.resolve('./creation_index_pattern')); + }); +} diff --git a/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts new file mode 100644 index 0000000000000..0b51b78265c25 --- /dev/null +++ b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/config.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + reportName: + 'Chrome X-Pack UI Functional Tests - transform - creation - runtime mappings & saved search', + }, + }; +} diff --git a/x-pack/test/functional/apps/transform/creation_runtime_mappings.ts b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts similarity index 96% rename from x-pack/test/functional/apps/transform/creation_runtime_mappings.ts rename to x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts index 3e595695a3da5..3f0adc5783893 100644 --- a/x-pack/test/functional/apps/transform/creation_runtime_mappings.ts +++ b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_runtime_mappings.ts @@ -7,9 +7,9 @@ import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; -import type { HistogramCharts } from '../../services/transform/wizard'; +import type { HistogramCharts } from '../../../../services/transform/wizard'; import { GroupByEntry, @@ -17,7 +17,7 @@ import { isPivotTransformTestData, LatestTransformTestData, PivotTransformTestData, -} from '.'; +} from '../../helpers'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); @@ -279,6 +279,11 @@ export default function ({ getService }: FtrProviderContext) { await transform.testExecution.logTestStep('has correct transform function selected'); await transform.wizard.assertSelectedTransformFunction('pivot'); + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); + await transform.testExecution.logTestStep('has correct runtime mappings settings'); await transform.wizard.assertRuntimeMappingsEditorSwitchExists(); await transform.wizard.assertRuntimeMappingsEditorSwitchCheckState(false); @@ -291,6 +296,12 @@ export default function ({ getService }: FtrProviderContext) { await transform.wizard.setRuntimeMappingsEditorContent(JSON.stringify(runtimeMappings)); await transform.wizard.applyRuntimeMappings(); + await transform.testExecution.logTestStep('displays an empty index preview'); + await transform.wizard.assertIndexPreviewEmpty(); + + await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`); + await transform.datePicker.quickSelect(10, 'y'); + await transform.testExecution.logTestStep('loads the index preview'); await transform.wizard.assertIndexPreviewLoaded(); @@ -439,7 +450,7 @@ export default function ({ getService }: FtrProviderContext) { if (isLatestTransformTestData(testData)) { const fromTime = 'Feb 7, 2016 @ 00:00:00.000'; const toTime = 'Feb 11, 2016 @ 23:59:54.000'; - await transform.wizard.setDiscoverTimeRange(fromTime, toTime); + await transform.datePicker.setTimeRange(fromTime, toTime); } await transform.testExecution.logTestStep( diff --git a/x-pack/test/functional/apps/transform/creation_saved_search.ts b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_saved_search.ts similarity index 95% rename from x-pack/test/functional/apps/transform/creation_saved_search.ts rename to x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_saved_search.ts index ee1d0e02899cc..9f985a16da98d 100644 --- a/x-pack/test/functional/apps/transform/creation_saved_search.ts +++ b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/creation_saved_search.ts @@ -7,14 +7,14 @@ import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; import { GroupByEntry, isLatestTransformTestData, isPivotTransformTestData, LatestTransformTestData, PivotTransformTestData, -} from '.'; +} from '../../helpers'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); @@ -144,6 +144,17 @@ export default function ({ getService }: FtrProviderContext) { await transform.testExecution.logTestStep('has correct transform function selected'); await transform.wizard.assertSelectedTransformFunction('pivot'); + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); + + await transform.testExecution.logTestStep('displays an empty index preview'); + await transform.wizard.assertIndexPreviewEmpty(); + + await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`); + await transform.datePicker.quickSelect(10, 'y'); + await transform.testExecution.logTestStep('loads the index preview'); await transform.wizard.assertIndexPreviewLoaded(); diff --git a/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/index.ts b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/index.ts new file mode 100644 index 0000000000000..943fb97200a7b --- /dev/null +++ b/x-pack/test/functional/apps/transform/creation/runtime_mappings_saved_search/index.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 { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + describe('transform - creation - runtime mappings & saved search', function () { + this.tags('transform'); + + before(async () => { + await transform.securityCommon.createTransformRoles(); + await transform.securityCommon.createTransformUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await transform.securityUI.logout(); + + await transform.securityCommon.cleanTransformUsers(); + await transform.securityCommon.cleanTransformRoles(); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); + + await transform.testResources.resetKibanaTimeZone(); + }); + + loadTestFile(require.resolve('./creation_saved_search')); + loadTestFile(require.resolve('./creation_runtime_mappings')); + }); +} diff --git a/x-pack/test/functional/apps/transform/cloning.ts b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts similarity index 97% rename from x-pack/test/functional/apps/transform/cloning.ts rename to x-pack/test/functional/apps/transform/edit_clone/cloning.ts index 8b03c0b26ee65..6b3bbac3f7b3d 100644 --- a/x-pack/test/functional/apps/transform/cloning.ts +++ b/x-pack/test/functional/apps/transform/edit_clone/cloning.ts @@ -10,8 +10,8 @@ import { isPivotTransform, TransformPivotConfig, } from '@kbn/transform-plugin/common/types/transform'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { getLatestTransformConfig } from '.'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getLatestTransformConfig } from '../helpers'; interface TestData { type: 'pivot' | 'latest'; @@ -418,6 +418,17 @@ export default function ({ getService }: FtrProviderContext) { ); } + await transform.testExecution.logTestStep( + `sets the date picker to the default '15 minutes ago'` + ); + await transform.datePicker.quickSelect(15, 'm'); + + await transform.testExecution.logTestStep('displays an empty index preview'); + await transform.wizard.assertIndexPreviewEmpty(); + + await transform.testExecution.logTestStep(`sets the date picker to '15 Years ago'`); + await transform.datePicker.quickSelect(); + await transform.testExecution.logTestStep('should load the index preview'); await transform.wizard.assertIndexPreviewLoaded(); diff --git a/x-pack/test/functional/apps/transform/edit_clone/config.ts b/x-pack/test/functional/apps/transform/edit_clone/config.ts new file mode 100644 index 0000000000000..9b3a878496a70 --- /dev/null +++ b/x-pack/test/functional/apps/transform/edit_clone/config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + reportName: 'Chrome X-Pack UI Functional Tests - transform - edit & clone', + }, + }; +} diff --git a/x-pack/test/functional/apps/transform/editing.ts b/x-pack/test/functional/apps/transform/edit_clone/editing.ts similarity index 99% rename from x-pack/test/functional/apps/transform/editing.ts rename to x-pack/test/functional/apps/transform/edit_clone/editing.ts index f96052ab28e18..5f767825c7c31 100644 --- a/x-pack/test/functional/apps/transform/editing.ts +++ b/x-pack/test/functional/apps/transform/edit_clone/editing.ts @@ -11,8 +11,8 @@ import type { TransformPivotConfig, } from '@kbn/transform-plugin/common/types/transform'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { getLatestTransformConfig, getPivotTransformConfig } from '.'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getLatestTransformConfig, getPivotTransformConfig } from '../helpers'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional/apps/transform/edit_clone/index.ts b/x-pack/test/functional/apps/transform/edit_clone/index.ts new file mode 100644 index 0000000000000..93dbaa51c396e --- /dev/null +++ b/x-pack/test/functional/apps/transform/edit_clone/index.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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + describe('transform - edit & clone', function () { + this.tags('transform'); + + before(async () => { + await transform.securityCommon.createTransformRoles(); + await transform.securityCommon.createTransformUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await transform.securityUI.logout(); + + await transform.securityCommon.cleanTransformUsers(); + await transform.securityCommon.cleanTransformRoles(); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); + + await transform.testResources.resetKibanaTimeZone(); + }); + + loadTestFile(require.resolve('./cloning')); + loadTestFile(require.resolve('./editing')); + }); +} diff --git a/x-pack/test/functional/apps/transform/feature_controls/config.ts b/x-pack/test/functional/apps/transform/feature_controls/config.ts new file mode 100644 index 0000000000000..f8ce309ed52e0 --- /dev/null +++ b/x-pack/test/functional/apps/transform/feature_controls/config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + reportName: 'Chrome X-Pack UI Functional Tests - transform - feature controls', + }, + }; +} diff --git a/x-pack/test/functional/apps/transform/feature_controls/index.ts b/x-pack/test/functional/apps/transform/feature_controls/index.ts index a00054c185438..987bd36172847 100644 --- a/x-pack/test/functional/apps/transform/feature_controls/index.ts +++ b/x-pack/test/functional/apps/transform/feature_controls/index.ts @@ -7,8 +7,31 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { - describe('feature controls', function () { +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + describe('transform - feature controls', function () { + this.tags('transform'); + + before(async () => { + await transform.securityCommon.createTransformRoles(); + await transform.securityCommon.createTransformUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await transform.securityUI.logout(); + + await transform.securityCommon.cleanTransformUsers(); + await transform.securityCommon.cleanTransformRoles(); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); + + await transform.testResources.resetKibanaTimeZone(); + }); + loadTestFile(require.resolve('./transform_security')); }); } diff --git a/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts b/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts index 5901ee60c7212..c630525d06cf6 100644 --- a/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts +++ b/x-pack/test/functional/apps/transform/feature_controls/transform_security.ts @@ -11,15 +11,15 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const security = getService('security'); - const PageObjects = getPageObjects(['common', 'settings', 'security']); + const pageObjects = getPageObjects(['common', 'settings', 'security']); const appsMenu = getService('appsMenu'); const managementMenu = getService('managementMenu'); describe('security', () => { before(async () => { await kibanaServer.savedObjects.cleanStandardList(); - await PageObjects.security.forceLogout(); - await PageObjects.common.navigateToApp('home'); + await pageObjects.security.forceLogout(); + await pageObjects.common.navigateToApp('home'); }); after(async () => { @@ -40,7 +40,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should not render the "Stack" section', async () => { - await PageObjects.common.navigateToApp('management'); + await pageObjects.common.navigateToApp('management'); const sections = (await managementMenu.getSections()).map((section) => section.sectionId); expect(sections).to.eql(['insightsAndAlerting', 'kibana']); }); @@ -59,7 +59,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); it('should render the "Data" section with Transform', async () => { - await PageObjects.common.navigateToApp('management'); + await pageObjects.common.navigateToApp('management'); const sections = await managementMenu.getSections(); expect(sections).to.have.length(1); expect(sections[0]).to.eql({ diff --git a/x-pack/test/functional/apps/transform/index.ts b/x-pack/test/functional/apps/transform/helpers.ts similarity index 65% rename from x-pack/test/functional/apps/transform/index.ts rename to x-pack/test/functional/apps/transform/helpers.ts index e87f72ed98880..c422ea6dfca0f 100644 --- a/x-pack/test/functional/apps/transform/index.ts +++ b/x-pack/test/functional/apps/transform/helpers.ts @@ -9,45 +9,7 @@ import { TransformLatestConfig, TransformPivotConfig, } from '@kbn/transform-plugin/common/types/transform'; -import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getService, loadTestFile }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const transform = getService('transform'); - - describe('transform', function () { - this.tags('transform'); - - before(async () => { - await transform.securityCommon.createTransformRoles(); - await transform.securityCommon.createTransformUsers(); - }); - - after(async () => { - // NOTE: Logout needs to happen before anything else to avoid flaky behavior - await transform.securityUI.logout(); - - await transform.securityCommon.cleanTransformUsers(); - await transform.securityCommon.cleanTransformRoles(); - - await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); - await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); - - await transform.testResources.resetKibanaTimeZone(); - }); - - loadTestFile(require.resolve('./permissions')); - loadTestFile(require.resolve('./creation_index_pattern')); - loadTestFile(require.resolve('./creation_saved_search')); - loadTestFile(require.resolve('./creation_runtime_mappings')); - loadTestFile(require.resolve('./cloning')); - loadTestFile(require.resolve('./editing')); - loadTestFile(require.resolve('./feature_controls')); - loadTestFile(require.resolve('./deleting')); - loadTestFile(require.resolve('./resetting')); - loadTestFile(require.resolve('./starting')); - }); -} export interface ComboboxOption { identifier: string; label: string; diff --git a/x-pack/test/functional/apps/transform/config.ts b/x-pack/test/functional/apps/transform/permissions/config.ts similarity index 83% rename from x-pack/test/functional/apps/transform/config.ts rename to x-pack/test/functional/apps/transform/permissions/config.ts index 17a471848867e..3771f59d47c61 100644 --- a/x-pack/test/functional/apps/transform/config.ts +++ b/x-pack/test/functional/apps/transform/permissions/config.ts @@ -8,13 +8,13 @@ import { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../../config.base.js')); + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); return { ...functionalConfig.getAll(), testFiles: [require.resolve('.')], junit: { - reportName: 'Chrome X-Pack UI Functional Tests - Transform', + reportName: 'Chrome X-Pack UI Functional Tests - transform - permissions', }, }; } diff --git a/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts b/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts index 59ad6d0d8dfa4..4969832b3600e 100644 --- a/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts +++ b/x-pack/test/functional/apps/transform/permissions/full_transform_access.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getPivotTransformConfig } from '..'; +import { getPivotTransformConfig } from '../helpers'; import { FtrProviderContext } from '../../../ftr_provider_context'; diff --git a/x-pack/test/functional/apps/transform/permissions/index.ts b/x-pack/test/functional/apps/transform/permissions/index.ts index 08f4043a62dee..30936edc877ef 100644 --- a/x-pack/test/functional/apps/transform/permissions/index.ts +++ b/x-pack/test/functional/apps/transform/permissions/index.ts @@ -7,8 +7,31 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { - describe('permissions', function () { +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + describe('transform - permissions', function () { + this.tags('transform'); + + before(async () => { + await transform.securityCommon.createTransformRoles(); + await transform.securityCommon.createTransformUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await transform.securityUI.logout(); + + await transform.securityCommon.cleanTransformUsers(); + await transform.securityCommon.cleanTransformRoles(); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); + + await transform.testResources.resetKibanaTimeZone(); + }); + loadTestFile(require.resolve('./full_transform_access')); loadTestFile(require.resolve('./read_transform_access')); }); diff --git a/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts b/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts index be808d606fdd6..918cd5c144a84 100644 --- a/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts +++ b/x-pack/test/functional/apps/transform/permissions/read_transform_access.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getPivotTransformConfig } from '..'; +import { getPivotTransformConfig } from '../helpers'; import { FtrProviderContext } from '../../../ftr_provider_context'; diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/config.ts b/x-pack/test/functional/apps/transform/start_reset_delete/config.ts new file mode 100644 index 0000000000000..edf34d16785c4 --- /dev/null +++ b/x-pack/test/functional/apps/transform/start_reset_delete/config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + reportName: 'Chrome X-Pack UI Functional Tests - transform - start reset & delete', + }, + }; +} diff --git a/x-pack/test/functional/apps/transform/deleting.ts b/x-pack/test/functional/apps/transform/start_reset_delete/deleting.ts similarity index 98% rename from x-pack/test/functional/apps/transform/deleting.ts rename to x-pack/test/functional/apps/transform/start_reset_delete/deleting.ts index ba49a5c0ae7f9..e76ef118fde0d 100644 --- a/x-pack/test/functional/apps/transform/deleting.ts +++ b/x-pack/test/functional/apps/transform/start_reset_delete/deleting.ts @@ -7,8 +7,8 @@ import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { getLatestTransformConfig, getPivotTransformConfig } from '.'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getLatestTransformConfig, getPivotTransformConfig } from '../helpers'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional/apps/transform/start_reset_delete/index.ts b/x-pack/test/functional/apps/transform/start_reset_delete/index.ts new file mode 100644 index 0000000000000..1a606339eb82a --- /dev/null +++ b/x-pack/test/functional/apps/transform/start_reset_delete/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const transform = getService('transform'); + + describe('transform - start reset & delete', function () { + this.tags('transform'); + + before(async () => { + await transform.securityCommon.createTransformRoles(); + await transform.securityCommon.createTransformUsers(); + }); + + after(async () => { + // NOTE: Logout needs to happen before anything else to avoid flaky behavior + await transform.securityUI.logout(); + + await transform.securityCommon.cleanTransformUsers(); + await transform.securityCommon.cleanTransformRoles(); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/ecommerce'); + + await transform.testResources.resetKibanaTimeZone(); + }); + + loadTestFile(require.resolve('./deleting')); + loadTestFile(require.resolve('./resetting')); + loadTestFile(require.resolve('./starting')); + }); +} diff --git a/x-pack/test/functional/apps/transform/resetting.ts b/x-pack/test/functional/apps/transform/start_reset_delete/resetting.ts similarity index 98% rename from x-pack/test/functional/apps/transform/resetting.ts rename to x-pack/test/functional/apps/transform/start_reset_delete/resetting.ts index 471f8f7681a20..ef62d9986813d 100644 --- a/x-pack/test/functional/apps/transform/resetting.ts +++ b/x-pack/test/functional/apps/transform/start_reset_delete/resetting.ts @@ -7,8 +7,8 @@ import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { getLatestTransformConfig, getPivotTransformConfig } from '.'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getLatestTransformConfig, getPivotTransformConfig } from '../helpers'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional/apps/transform/starting.ts b/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts similarity index 98% rename from x-pack/test/functional/apps/transform/starting.ts rename to x-pack/test/functional/apps/transform/start_reset_delete/starting.ts index c2940a95aaece..1fccec9a9192b 100644 --- a/x-pack/test/functional/apps/transform/starting.ts +++ b/x-pack/test/functional/apps/transform/start_reset_delete/starting.ts @@ -6,8 +6,8 @@ */ import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { getLatestTransformConfig, getPivotTransformConfig } from '.'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { getLatestTransformConfig, getPivotTransformConfig } from '../helpers'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional/es_archives/cases/migrations/7.10.0/data.json.gz b/x-pack/test/functional/es_archives/cases/migrations/7.10.0/data.json.gz deleted file mode 100644 index 607977990594f..0000000000000 Binary files a/x-pack/test/functional/es_archives/cases/migrations/7.10.0/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json b/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json deleted file mode 100644 index b1b6c468c3945..0000000000000 --- a/x-pack/test/functional/es_archives/cases/migrations/7.10.0/mappings.json +++ /dev/null @@ -1,2519 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "7b44fba6773e37c806ce290ea9b7024e", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", - "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", - "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", - "cases": "32aa96a6d3855ddda53010ae2048ac22", - "cases-comments": "c2061fb929f585df57425102fa928b4b", - "cases-configure": "42711cbb311976c0687853f4c1354572", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "c63748b75f39d0c54de12d12c1ccbc20", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", - "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862", - "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", - "exception-list": "497afa2f881a675d72d58e20057f3d8b", - "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", - "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", - "fleet-agents": "034346488514b7058a79140b19ddf631", - "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", - "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", - "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", - "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", - "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", - "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "d33c68a69ff1e78c9888dedd2164ac22", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "4a05b35c3a3a58fbc72dd0202dc3487f", - "maps-telemetry": "5ef305b18111b77789afefbd36b66171", - "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "namespaces": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "5c4b9a6effceb17ae8a0ab22d0c49767", - "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", - "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", - "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", - "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", - "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", - "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", - "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc", - "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-indices": { - "properties": { - "error": { - "type": "keyword" - }, - "metric": { - "type": "keyword" - }, - "onboarding": { - "type": "keyword" - }, - "sourcemap": { - "type": "keyword" - }, - "span": { - "type": "keyword" - }, - "transaction": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "dynamic": "false", - "type": "object" - }, - "app_search_telemetry": { - "dynamic": "false", - "type": "object" - }, - "application_usage_daily": { - "dynamic": "false", - "properties": { - "timestamp": { - "type": "date" - } - } - }, - "application_usage_totals": { - "dynamic": "false", - "type": "object" - }, - "application_usage_transactional": { - "dynamic": "false", - "type": "object" - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad-template": { - "dynamic": "false", - "properties": { - "help": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "tags": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "template_key": { - "type": "keyword" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "connector_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "false", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "endpoint:user-artifact": { - "properties": { - "body": { - "type": "binary" - }, - "compressionAlgorithm": { - "index": false, - "type": "keyword" - }, - "created": { - "index": false, - "type": "date" - }, - "decodedSha256": { - "index": false, - "type": "keyword" - }, - "decodedSize": { - "index": false, - "type": "long" - }, - "encodedSha256": { - "type": "keyword" - }, - "encodedSize": { - "index": false, - "type": "long" - }, - "encryptionAlgorithm": { - "index": false, - "type": "keyword" - }, - "identifier": { - "type": "keyword" - } - } - }, - "endpoint:user-artifact-manifest": { - "properties": { - "created": { - "index": false, - "type": "date" - }, - "schemaVersion": { - "type": "keyword" - }, - "semanticVersion": { - "index": false, - "type": "keyword" - }, - "artifacts": { - "type": "nested", - "properties": { - "policyId": { - "type": "keyword", - "index": false - }, - "artifactId": { - "type": "keyword", - "index": false - } - } - } - } - }, - "epm-packages": { - "properties": { - "es_index_patterns": { - "enabled": false, - "type": "object" - }, - "installed_es": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "installed_kibana": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "internal": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "removable": { - "type": "boolean" - }, - "version": { - "type": "keyword" - } - } - }, - "exception-list": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "immutable": { - "type": "boolean" - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "exception-list-agnostic": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "immutable": { - "type": "boolean" - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "fleet-agent-actions": { - "properties": { - "agent_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "data": { - "type": "binary" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agent-events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "active": { - "type": "boolean" - }, - "config_id": { - "type": "keyword" - }, - "config_revision": { - "type": "integer" - }, - "current_error_events": { - "index": false, - "type": "text" - }, - "default_api_key": { - "type": "binary" - }, - "default_api_key_id": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_checkin_status": { - "type": "keyword" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "flattened" - }, - "packages": { - "type": "keyword" - }, - "shared_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "unenrolled_at": { - "type": "date" - }, - "unenrollment_started_at": { - "type": "date" - }, - "updated_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "flattened" - }, - "version": { - "type": "keyword" - } - } - }, - "fleet-enrollment-api-keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { - "type": "binary" - }, - "api_key_id": { - "type": "keyword" - }, - "config_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "expire_at": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "properties": { - "description": { - "type": "text" - }, - "fields": { - "properties": { - "container": { - "type": "keyword" - }, - "host": { - "type": "keyword" - }, - "pod": { - "type": "keyword" - }, - "tiebreaker": { - "type": "keyword" - }, - "timestamp": { - "type": "keyword" - } - } - }, - "inventoryDefaultView": { - "type": "keyword" - }, - "logAlias": { - "type": "keyword" - }, - "logColumns": { - "properties": { - "fieldColumn": { - "properties": { - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - } - } - }, - "messageColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - }, - "timestampColumn": { - "properties": { - "id": { - "type": "keyword" - } - } - } - }, - "type": "nested" - }, - "metricAlias": { - "type": "keyword" - }, - "metricsExplorerDefaultView": { - "type": "keyword" - }, - "name": { - "type": "text" - } - } - }, - "ingest-agent-policies": { - "properties": { - "description": { - "type": "text" - }, - "is_default": { - "type": "boolean" - }, - "monitoring_enabled": { - "index": false, - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "package_configs": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest-outputs": { - "properties": { - "ca_sha256": { - "index": false, - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "ingest-package-policies": { - "properties": { - "config_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "enabled": false, - "properties": { - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "streams": { - "properties": { - "compiled_stream": { - "type": "flattened" - }, - "config": { - "type": "flattened" - }, - "data_stream": { - "properties": { - "dataset": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "revision": { - "type": "integer" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest_manager_settings": { - "properties": { - "agent_auto_upgrade": { - "type": "keyword" - }, - "has_seen_add_data_notice": { - "index": false, - "type": "boolean" - }, - "kibana_ca_sha256": { - "type": "keyword" - }, - "kibana_url": { - "type": "keyword" - }, - "package_auto_upgrade": { - "type": "keyword" - } - } - }, - "inventory-view": { - "properties": { - "accountId": { - "type": "keyword" - }, - "autoBounds": { - "type": "boolean" - }, - "autoReload": { - "type": "boolean" - }, - "boundsOverride": { - "properties": { - "max": { - "type": "integer" - }, - "min": { - "type": "integer" - } - } - }, - "customMetrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "label": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "customOptions": { - "properties": { - "field": { - "type": "keyword" - }, - "text": { - "type": "keyword" - } - }, - "type": "nested" - }, - "filterQuery": { - "properties": { - "expression": { - "type": "keyword" - }, - "kind": { - "type": "keyword" - } - } - }, - "groupBy": { - "properties": { - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - }, - "legend": { - "properties": { - "palette": { - "type": "keyword" - }, - "reverseColors": { - "type": "boolean" - }, - "steps": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "label": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "nodeType": { - "type": "keyword" - }, - "region": { - "type": "keyword" - }, - "sort": { - "properties": { - "by": { - "type": "keyword" - }, - "direction": { - "type": "keyword" - } - } - }, - "time": { - "type": "long" - }, - "view": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "description": { - "type": "text" - }, - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "enabled": false, - "type": "object" - }, - "metrics-explorer-view": { - "properties": { - "chartOptions": { - "properties": { - "stack": { - "type": "boolean" - }, - "type": { - "type": "keyword" - }, - "yAxisMode": { - "type": "keyword" - } - } - }, - "currentTimerange": { - "properties": { - "from": { - "type": "keyword" - }, - "interval": { - "type": "keyword" - }, - "to": { - "type": "keyword" - } - } - }, - "name": { - "type": "keyword" - }, - "options": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "filterQuery": { - "type": "keyword" - }, - "forceInterval": { - "type": "boolean" - }, - "groupBy": { - "type": "keyword" - }, - "limit": { - "type": "integer" - }, - "metrics": { - "properties": { - "aggregation": { - "type": "keyword" - }, - "color": { - "type": "keyword" - }, - "field": { - "type": "keyword" - }, - "label": { - "type": "keyword" - } - }, - "type": "nested" - }, - "source": { - "type": "keyword" - } - } - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "config": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "space": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "namespaces": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "index": false, - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "index": false, - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "sort": { - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "search-telemetry": { - "dynamic": "false", - "type": "object" - }, - "siem-detection-engine-rule-actions": { - "properties": { - "actions": { - "properties": { - "action_type_id": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alertThrottle": { - "type": "keyword" - }, - "ruleAlertId": { - "type": "keyword" - }, - "ruleThrottle": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "bulkCreateTimeDurations": { - "type": "float" - }, - "gap": { - "type": "text" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastLookBackDate": { - "type": "date" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "searchAfterTimeDurations": { - "type": "float" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "excludedRowRendererIds": { - "type": "text" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "properties": { - "columnId": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "templateTimelineId": { - "type": "text" - }, - "templateTimelineVersion": { - "type": "integer" - }, - "timelineType": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "properties": { - "errorMessage": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "indexName": { - "type": "keyword" - }, - "lastCompletedStep": { - "type": "long" - }, - "locked": { - "type": "date" - }, - "newIndexName": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexOptions": { - "properties": { - "openAndClose": { - "type": "boolean" - }, - "queueSettings": { - "properties": { - "queuedAt": { - "type": "long" - }, - "startedAt": { - "type": "long" - } - } - } - } - }, - "reindexTaskId": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexTaskPercComplete": { - "type": "float" - }, - "runningReindexCount": { - "type": "integer" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "properties": { - "certAgeThreshold": { - "type": "long" - }, - "certExpirationThreshold": { - "type": "long" - }, - "heartbeatIndices": { - "type": "keyword" - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "workplace_search_telemetry": { - "dynamic": "false", - "type": "object" - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json b/x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json new file mode 100644 index 0000000000000..2e4edc66d2562 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/cases/7.10.0/data.json @@ -0,0 +1,150 @@ +{ + "id": "e1900ac0-017f-11eb-93f8-d161651bf509", + "attributes": { + "closed_at": null, + "closed_by": null, + "connector_id": "connector-1", + "created_at": "2020-09-28T11:43:52.158Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "description": "This is a brand new case of a bad meanie defacing data", + "external_service": null, + "status": "open", + "tags": [ + "defacement" + ], + "title": "Super Bad Security Issue", + "updated_at": null, + "updated_by": null + }, + "coreMigrationVersion": "7.10.0", + "migrationVersion": { + "alert": "7.10.0" + }, + "references": [ + ], + "type": "cases", + "updated_at": "2020-09-28T11:43:52.171Z" +} + +{ + "id": "e22a7600-017f-11eb-93f8-d161651bf509", + "attributes": { + "action": "create", + "action_at": "2020-09-28T11:43:52.158Z", + "action_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "action_field": [ + "description", + "status", + "tags", + "title" + ], + "new_value": "{\"description\":\"This is a brand new case of a bad meanie defacing data\",\"title\":\"Super Bad Security Issue\",\"tags\":[\"defacement\"]}", + "old_value": null + }, + "references": [ + { + "id": "e1900ac0-017f-11eb-93f8-d161651bf509", + "name": "associated-cases", + "type": "cases" + } + ], + "coreMigrationVersion": "7.10.0", + "type": "cases-user-actions", + "updated_at": "2020-09-28T11:43:53.184Z" +} + +{ + "id": "a22a7600-017f-11eb-93f8-d161651bf509", + "attributes": { + "action": "update", + "action_at": "2020-09-28T11:53:52.158Z", + "action_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "action_field": [ + "connector_id" + ], + "new_value": "b1900ac0-017f-11eb-93f8-d161651bf509", + "old_value": "c1900ac0-017f-11eb-93f8-d161651bf509" + }, + "references": [ + { + "id": "e1900ac0-017f-11eb-93f8-d161651bf509", + "name": "associated-cases", + "type": "cases" + } + ], + "coreMigrationVersion": "7.10.0", + "type": "cases-user-actions", + "updated_at": "2020-09-28T11:43:53.184Z" +} + +{ + "id": "da677740-1ac7-11eb-b5a3-25ee88122510", + "attributes": { + "comment": "This is a cool comment", + "created_at": "2020-10-30T15:52:02.984Z", + "created_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "pushed_at": null, + "pushed_by": null, + "updated_at": null, + "updated_by": null + }, + "references": [ + { + "id": "e1900ac0-017f-11eb-93f8-d161651bf509", + "name": "associated-cases", + "type": "cases" + } + ], + "coreMigrationVersion": "7.10.0", + "type": "cases-comments", + "updated_at": "2020-10-30T15:52:02.996Z" +} + +{ + "id": "db027ec0-1ac7-11eb-b5a3-25ee88122510", + "attributes": { + "action": "create", + "action_at": "2020-10-30T15:52:02.984Z", + "action_by": { + "email": null, + "full_name": null, + "username": "elastic" + }, + "action_field": [ + "comment" + ], + "new_value": "This is a cool comment", + "old_value": null + }, + "references": [ + { + "id": "e1900ac0-017f-11eb-93f8-d161651bf509", + "name": "associated-cases", + "type": "cases" + }, + { + "id": "da677740-1ac7-11eb-b5a3-25ee88122510", + "name": "associated-cases-comments", + "type": "cases-comments" + } + ], + "coreMigrationVersion": "7.10.0", + "type": "cases-user-actions", + "updated_at": "2020-10-30T15:52:04.012Z" +} diff --git a/x-pack/test/functional/services/transform/date_picker.ts b/x-pack/test/functional/services/transform/date_picker.ts new file mode 100644 index 0000000000000..941a506db6109 --- /dev/null +++ b/x-pack/test/functional/services/transform/date_picker.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; 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 function TransformDatePickerProvider({ getService, getPageObjects }: FtrProviderContext) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['timePicker']); + + return { + async assertSuperDatePickerToggleQuickMenuButtonExists() { + await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton'); + }, + + async openSuperDatePicker() { + await this.assertSuperDatePickerToggleQuickMenuButtonExists(); + await testSubjects.click('superDatePickerToggleQuickMenuButton'); + await testSubjects.existOrFail('superDatePickerQuickMenu'); + }, + + async quickSelect(timeValue: number = 15, timeUnit: string = 'y') { + await this.openSuperDatePicker(); + const quickMenuElement = await testSubjects.find('superDatePickerQuickMenu'); + + // No test subject, defaults to select `"Years"` to look back 15 years instead of 15 minutes. + await find.selectValue(`[aria-label*="Time value"]`, timeValue.toString()); + await find.selectValue(`[aria-label*="Time unit"]`, timeUnit); + + // Apply + const applyButton = await quickMenuElement.findByClassName('euiQuickSelect__applyButton'); + const actualApplyButtonText = await applyButton.getVisibleText(); + expect(actualApplyButtonText).to.be('Apply'); + + await applyButton.click(); + await testSubjects.missingOrFail('superDatePickerQuickMenu'); + }, + + async setTimeRange(fromTime: string, toTime: string) { + await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + }, + }; +} diff --git a/x-pack/test/functional/services/transform/discover.ts b/x-pack/test/functional/services/transform/discover.ts index 944e65b73f6e2..303bc9b171f80 100644 --- a/x-pack/test/functional/services/transform/discover.ts +++ b/x-pack/test/functional/services/transform/discover.ts @@ -10,7 +10,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export function TransformDiscoverProvider({ getService }: FtrProviderContext) { - const find = getService('find'); const testSubjects = getService('testSubjects'); return { @@ -28,6 +27,8 @@ export function TransformDiscoverProvider({ getService }: FtrProviderContext) { }, async assertNoResults(expectedDestinationIndex: string) { + await testSubjects.missingOrFail('unifiedHistogramQueryHits'); + // Discover should use the destination index pattern const actualIndexPatternSwitchLinkText = await ( await testSubjects.find('discover-dataView-switch-link') @@ -39,29 +40,5 @@ export function TransformDiscoverProvider({ getService }: FtrProviderContext) { await testSubjects.existOrFail('discoverNoResults'); }, - - async assertSuperDatePickerToggleQuickMenuButtonExists() { - await testSubjects.existOrFail('superDatePickerToggleQuickMenuButton'); - }, - - async openSuperDatePicker() { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - await testSubjects.existOrFail('superDatePickerQuickMenu'); - }, - - async quickSelectYears() { - const quickMenuElement = await testSubjects.find('superDatePickerQuickMenu'); - - // No test subject, select "Years" to look back 15 years instead of 15 minutes. - await find.selectValue(`[aria-label*="Time unit"]`, 'y'); - - // Apply - const applyButton = await quickMenuElement.findByClassName('euiQuickSelect__applyButton'); - const actualApplyButtonText = await applyButton.getVisibleText(); - expect(actualApplyButtonText).to.be('Apply'); - - await applyButton.click(); - await testSubjects.existOrFail('unifiedHistogramQueryHits'); - }, }; } diff --git a/x-pack/test/functional/services/transform/index.ts b/x-pack/test/functional/services/transform/index.ts index 75f0df67f0919..61461bafe34b5 100644 --- a/x-pack/test/functional/services/transform/index.ts +++ b/x-pack/test/functional/services/transform/index.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { TransformAPIProvider } from './api'; import { TransformEditFlyoutProvider } from './edit_flyout'; +import { TransformDatePickerProvider } from './date_picker'; import { TransformDiscoverProvider } from './discover'; import { TransformManagementProvider } from './management'; import { TransformNavigationProvider } from './navigation'; @@ -25,6 +26,7 @@ import { MachineLearningTestResourcesProvider } from '../ml/test_resources'; export function TransformProvider(context: FtrProviderContext) { const api = TransformAPIProvider(context); const mlApi = MachineLearningAPIProvider(context); + const datePicker = TransformDatePickerProvider(context); const discover = TransformDiscoverProvider(context); const editFlyout = TransformEditFlyoutProvider(context); const management = TransformManagementProvider(context); @@ -39,6 +41,7 @@ export function TransformProvider(context: FtrProviderContext) { return { api, + datePicker, discover, editFlyout, management, diff --git a/x-pack/test/functional/services/transform/navigation.ts b/x-pack/test/functional/services/transform/navigation.ts index 396a99b1b6673..be579cdc0fb42 100644 --- a/x-pack/test/functional/services/transform/navigation.ts +++ b/x-pack/test/functional/services/transform/navigation.ts @@ -8,11 +8,11 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function TransformNavigationProvider({ getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common']); + const pageObjects = getPageObjects(['common']); return { async navigateTo() { - return await PageObjects.common.navigateToApp('transform'); + return await pageObjects.common.navigateToApp('transform'); }, }; } diff --git a/x-pack/test/functional/services/transform/security_ui.ts b/x-pack/test/functional/services/transform/security_ui.ts index 07d3c2759f42c..365f2dfc2e7d4 100644 --- a/x-pack/test/functional/services/transform/security_ui.ts +++ b/x-pack/test/functional/services/transform/security_ui.ts @@ -12,15 +12,15 @@ export function TransformSecurityUIProvider( { getPageObjects }: FtrProviderContext, transformSecurityCommon: TransformSecurityCommon ) { - const PageObjects = getPageObjects(['security']); + const pageObjects = getPageObjects(['security']); return { async loginAs(user: USER) { const password = transformSecurityCommon.getPasswordForUser(user); - await PageObjects.security.forceLogout(); + await pageObjects.security.forceLogout(); - await PageObjects.security.login(user, password, { + await pageObjects.security.login(user, password, { expectSuccess: true, }); }, @@ -34,7 +34,7 @@ export function TransformSecurityUIProvider( }, async logout() { - await PageObjects.security.forceLogout(); + await pageObjects.security.forceLogout(); }, }; } diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index df65911cb4098..e1370706d2902 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -29,7 +29,7 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi const ml = getService('ml'); const toasts = getService('toasts'); - const PageObjects = getPageObjects(['discover', 'timePicker']); + const pageObjects = getPageObjects(['discover', 'timePicker']); return { async clickNextButton() { @@ -80,6 +80,10 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi await testSubjects.existOrFail(selector); }, + async assertIndexPreviewEmpty() { + await this.assertIndexPreviewExists('empty'); + }, + async assertIndexPreviewLoaded() { await this.assertIndexPreviewExists('loaded'); }, @@ -995,19 +999,14 @@ export function TransformWizardProvider({ getService, getPageObjects }: FtrProvi async redirectToDiscover() { await retry.tryForTime(60 * 1000, async () => { await testSubjects.click('transformWizardCardDiscover'); - await PageObjects.discover.isDiscoverAppOnScreen(); + await pageObjects.discover.isDiscoverAppOnScreen(); }); }, - async setDiscoverTimeRange(fromTime: string, toTime: string) { - await PageObjects.discover.isDiscoverAppOnScreen(); - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - }, - async assertDiscoverContainField(field: string) { - await PageObjects.discover.isDiscoverAppOnScreen(); + await pageObjects.discover.isDiscoverAppOnScreen(); await retry.tryForTime(60 * 1000, async () => { - const allFields = await PageObjects.discover.getAllFieldNames(); + const allFields = await pageObjects.discover.getAllFieldNames(); if (Array.isArray(allFields)) { // For some reasons, Discover returns fields with dot (e.g '.avg') with extra space const fields = allFields.map((n) => n.replace('.​', '.')); diff --git a/x-pack/test/functional_basic/config.ts b/x-pack/test/functional_basic/apps/ml/config.base.ts similarity index 90% rename from x-pack/test/functional_basic/config.ts rename to x-pack/test/functional_basic/apps/ml/config.base.ts index f35ece0ce5d16..fc431b27ea457 100644 --- a/x-pack/test/functional_basic/config.ts +++ b/x-pack/test/functional_basic/apps/ml/config.base.ts @@ -9,7 +9,7 @@ import { FtrConfigProviderContext } from '@kbn/test'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const xpackFunctionalConfig = await readConfigFile( - require.resolve('../functional/config.base.js') + require.resolve('../../../functional/config.base.js') ); return { @@ -24,10 +24,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { 'xpack.security.authc.api_key.enabled=true', ], }, - testFiles: [require.resolve('./apps')], junit: { ...xpackFunctionalConfig.get('junit'), - reportName: 'Chrome X-Pack UI Functional Tests Basic License', + reportName: 'Chrome X-Pack UI Functional Tests Basic License - ml', }, }; } diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/group1/config.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group1/config.ts new file mode 100644 index 0000000000000..4f6a1ca9ba3dc --- /dev/null +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group1/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const baseConfig = await readConfigFile(require.resolve('../../config.base.ts')); + + return { + // default to the ml/config.base.ts + ...baseConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...baseConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - ml - data visualizer - group1', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/ml/index.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group1/index.ts similarity index 76% rename from x-pack/test/functional_basic/apps/ml/index.ts rename to x-pack/test/functional_basic/apps/ml/data_visualizer/group1/index.ts index dbdab2cc0a4b2..37295dda128ad 100644 --- a/x-pack/test/functional_basic/apps/ml/index.ts +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group1/index.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('machine learning basic license', function () { + describe('machine learning basic license - data visualizer - group 1', function () { this.tags(['skipFirefox', 'ml']); before(async () => { @@ -34,7 +34,9 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.testResources.resetKibanaTimeZone(); }); - loadTestFile(require.resolve('./permissions')); - loadTestFile(require.resolve('./data_visualizer')); + // The file data visualizer should work the same as with a trial license + loadTestFile( + require.resolve('../../../../../functional/apps/ml/data_visualizer/file_data_visualizer') + ); }); } diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/group2/config.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group2/config.ts new file mode 100644 index 0000000000000..0e16eaddb3b3f --- /dev/null +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group2/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const baseConfig = await readConfigFile(require.resolve('../../config.base.ts')); + + return { + // default to the ml/config.base.ts + ...baseConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...baseConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - ml - data visualizer - group2', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/group2/index.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group2/index.ts new file mode 100644 index 0000000000000..e1ed6d554a398 --- /dev/null +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group2/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('machine learning basic license - data visualizer - group 2', function () { + this.tags(['skipFirefox', 'ml']); + + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + + await ml.testResources.deleteSavedSearches(); + + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/module_sample_ecommerce'); + + await ml.testResources.resetKibanaTimeZone(); + }); + + // The data visualizer should work the same as with a trial license, except the missing create actions + // That's why the 'basic' version of 'index_data_visualizer_actions_panel' is loaded here + loadTestFile( + require.resolve('../../../../../functional/apps/ml/data_visualizer/index_data_visualizer') + ); + }); +} diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/config.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/config.ts new file mode 100644 index 0000000000000..eb81a95799000 --- /dev/null +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const baseConfig = await readConfigFile(require.resolve('../../config.base.ts')); + + return { + // default to the ml/config.base.ts + ...baseConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...baseConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - ml - data visualizer - group3', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/index.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/index.ts new file mode 100644 index 0000000000000..18a5dfaec2d60 --- /dev/null +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('machine learning basic license - data visualizer - group 3', function () { + this.tags(['skipFirefox', 'ml']); + + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + + await ml.testResources.deleteSavedSearches(); + + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/module_sample_ecommerce'); + + await ml.testResources.resetKibanaTimeZone(); + }); + + // The data visualizer should work the same as with a trial license, except the missing create actions + // That's why the 'basic' version of 'index_data_visualizer_actions_panel' is loaded here + loadTestFile( + require.resolve( + '../../../../../functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_discover' + ) + ); + loadTestFile(require.resolve('./index_data_visualizer_actions_panel')); + }); +} diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/index_data_visualizer_actions_panel.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts similarity index 97% rename from x-pack/test/functional_basic/apps/ml/data_visualizer/index_data_visualizer_actions_panel.ts rename to x-pack/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts index 2fcdf957f8909..f8e2c83a1afd7 100644 --- a/x-pack/test/functional_basic/apps/ml/data_visualizer/index_data_visualizer_actions_panel.ts +++ b/x-pack/test/functional_basic/apps/ml/data_visualizer/group3/index_data_visualizer_actions_panel.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { FtrProviderContext } from '../../../ftr_provider_context'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); diff --git a/x-pack/test/functional_basic/apps/ml/data_visualizer/index.ts b/x-pack/test/functional_basic/apps/ml/data_visualizer/index.ts deleted file mode 100644 index 4d38e6a144a78..0000000000000 --- a/x-pack/test/functional_basic/apps/ml/data_visualizer/index.ts +++ /dev/null @@ -1,29 +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 { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('data visualizer', function () { - // The file data visualizer should work the same as with a trial license - loadTestFile( - require.resolve('../../../../functional/apps/ml/data_visualizer/file_data_visualizer') - ); - - // The data visualizer should work the same as with a trial license, except the missing create actions - // That's why the 'basic' version of 'index_data_visualizer_actions_panel' is loaded here - loadTestFile( - require.resolve('../../../../functional/apps/ml/data_visualizer/index_data_visualizer') - ); - loadTestFile( - require.resolve( - '../../../../functional/apps/ml/data_visualizer/index_data_visualizer_grid_in_discover' - ) - ); - loadTestFile(require.resolve('./index_data_visualizer_actions_panel')); - }); -} diff --git a/x-pack/test/functional_basic/apps/ml/permissions/config.ts b/x-pack/test/functional_basic/apps/ml/permissions/config.ts new file mode 100644 index 0000000000000..048bbc8f0d1f3 --- /dev/null +++ b/x-pack/test/functional_basic/apps/ml/permissions/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const baseConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + // default to the ml/config.base.ts + ...baseConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...baseConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - ml - permissions', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/ml/permissions/index.ts b/x-pack/test/functional_basic/apps/ml/permissions/index.ts index b8d57bc4a9525..53e78b4d08c15 100644 --- a/x-pack/test/functional_basic/apps/ml/permissions/index.ts +++ b/x-pack/test/functional_basic/apps/ml/permissions/index.ts @@ -7,8 +7,33 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { - describe('permissions', function () { +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + + describe('machine learning basic license - permissions', function () { + this.tags(['skipFirefox', 'ml']); + + before(async () => { + await ml.securityCommon.createMlRoles(); + await ml.securityCommon.createMlUsers(); + }); + + after(async () => { + await ml.securityCommon.cleanMlUsers(); + await ml.securityCommon.cleanMlRoles(); + + await ml.testResources.deleteSavedSearches(); + + await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_module_sample_ecommerce'); + + await esArchiver.unload('x-pack/test/functional/es_archives/ml/farequote'); + await esArchiver.unload('x-pack/test/functional/es_archives/ml/module_sample_ecommerce'); + + await ml.testResources.resetKibanaTimeZone(); + }); + loadTestFile(require.resolve('./full_ml_access')); loadTestFile(require.resolve('./read_ml_access')); loadTestFile(require.resolve('./no_ml_access')); diff --git a/x-pack/test/functional_basic/apps/transform/config.base.ts b/x-pack/test/functional_basic/apps/transform/config.base.ts new file mode 100644 index 0000000000000..776fdb2c18900 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/config.base.ts @@ -0,0 +1,32 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile( + require.resolve('../../../functional/config.base.js') + ); + + return { + // default to the xpack functional config + ...xpackFunctionalConfig.getAll(), + esTestCluster: { + ...xpackFunctionalConfig.get('esTestCluster'), + license: 'basic', + serverArgs: [ + 'xpack.license.self_generated.type=basic', + 'xpack.security.enabled=true', + 'xpack.security.authc.api_key.enabled=true', + ], + }, + junit: { + ...xpackFunctionalConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - transform', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/transform/creation/index_pattern/config.ts b/x-pack/test/functional_basic/apps/transform/creation/index_pattern/config.ts new file mode 100644 index 0000000000000..469555d84b78d --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/creation/index_pattern/config.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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const transformConfig = await readConfigFile(require.resolve('../../config.base.ts')); + + return { + // default to the transform/config.base.ts + ...transformConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...transformConfig.get('junit'), + reportName: + 'Chrome X-Pack UI Functional Tests Basic License - transform - creation - index_pattern', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/transform/creation/index_pattern/index.ts b/x-pack/test/functional_basic/apps/transform/creation/index_pattern/index.ts new file mode 100644 index 0000000000000..d78248e6e72b3 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/creation/index_pattern/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('transform basic license', function () { + this.tags(['skipFirefox', 'transform']); + + // The transform UI should work the same as with a trial license + loadTestFile( + require.resolve('../../../../../functional/apps/transform/creation/index_pattern') + ); + }); +} diff --git a/x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/config.ts b/x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/config.ts new file mode 100644 index 0000000000000..91daf3d099007 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/config.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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const transformConfig = await readConfigFile(require.resolve('../../config.base.ts')); + + return { + // default to the transform/config.base.ts + ...transformConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...transformConfig.get('junit'), + reportName: + 'Chrome X-Pack UI Functional Tests Basic License - transform - creation - runtime mappings & saved search', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/index.ts b/x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/index.ts new file mode 100644 index 0000000000000..13978e3703e34 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/creation/runtime_mappings_saved_search/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('transform basic license', function () { + this.tags(['skipFirefox', 'transform']); + + // The transform UI should work the same as with a trial license + loadTestFile( + require.resolve( + '../../../../../functional/apps/transform/creation/runtime_mappings_saved_search' + ) + ); + }); +} diff --git a/x-pack/test/functional_basic/apps/transform/edit_clone/config.ts b/x-pack/test/functional_basic/apps/transform/edit_clone/config.ts new file mode 100644 index 0000000000000..35650196f37e5 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/edit_clone/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const transformConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + // default to the transform/config.base.ts + ...transformConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...transformConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - transform - edit & clone', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/transform/index.ts b/x-pack/test/functional_basic/apps/transform/edit_clone/index.ts similarity index 76% rename from x-pack/test/functional_basic/apps/transform/index.ts rename to x-pack/test/functional_basic/apps/transform/edit_clone/index.ts index f21d0674dcd24..7c55473b49eb5 100644 --- a/x-pack/test/functional_basic/apps/transform/index.ts +++ b/x-pack/test/functional_basic/apps/transform/edit_clone/index.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('transform basic license', function () { this.tags(['skipFirefox', 'transform']); // The transform UI should work the same as with a trial license - loadTestFile(require.resolve('../../../functional/apps/transform')); + loadTestFile(require.resolve('../../../../functional/apps/transform/edit_clone')); }); } diff --git a/x-pack/test/functional_basic/apps/transform/feature_controls/config.ts b/x-pack/test/functional_basic/apps/transform/feature_controls/config.ts new file mode 100644 index 0000000000000..29f57e3be1dbc --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/feature_controls/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const transformConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + // default to the transform/config.base.ts + ...transformConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...transformConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - transform - feature controls', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/transform/feature_controls/index.ts b/x-pack/test/functional_basic/apps/transform/feature_controls/index.ts new file mode 100644 index 0000000000000..e3790ac777de2 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/feature_controls/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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('transform basic license', function () { + this.tags(['skipFirefox', 'transform']); + + // The transform UI should work the same as with a trial license + loadTestFile(require.resolve('../../../../functional/apps/transform/feature_controls')); + }); +} diff --git a/x-pack/test/functional_basic/apps/transform/permissions/config.ts b/x-pack/test/functional_basic/apps/transform/permissions/config.ts new file mode 100644 index 0000000000000..0babb10724110 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/permissions/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const transformConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + // default to the transform/config.base.ts + ...transformConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...transformConfig.get('junit'), + reportName: 'Chrome X-Pack UI Functional Tests Basic License - transform - permissions', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/index.ts b/x-pack/test/functional_basic/apps/transform/permissions/index.ts similarity index 50% rename from x-pack/test/functional_basic/apps/index.ts rename to x-pack/test/functional_basic/apps/transform/permissions/index.ts index d765aeaa9e6ef..c0bf1ec9e3dc6 100644 --- a/x-pack/test/functional_basic/apps/index.ts +++ b/x-pack/test/functional_basic/apps/transform/permissions/index.ts @@ -5,11 +5,13 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; +import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { - describe('apps', function () { - loadTestFile(require.resolve('./ml')); - loadTestFile(require.resolve('./transform')); + describe('transform basic license', function () { + this.tags(['skipFirefox', 'transform']); + + // The transform UI should work the same as with a trial license + loadTestFile(require.resolve('../../../../functional/apps/transform/permissions')); }); } diff --git a/x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts b/x-pack/test/functional_basic/apps/transform/start_reset_delete/config.ts new file mode 100644 index 0000000000000..6922e0f70c5a5 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/start_reset_delete/config.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 { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const transformConfig = await readConfigFile(require.resolve('../config.base.ts')); + + return { + // default to the transform/config.base.ts + ...transformConfig.getAll(), + testFiles: [require.resolve('.')], + junit: { + ...transformConfig.get('junit'), + reportName: + 'Chrome X-Pack UI Functional Tests Basic License - transform - start reset & delete', + }, + }; +} diff --git a/x-pack/test/functional_basic/apps/transform/start_reset_delete/index.ts b/x-pack/test/functional_basic/apps/transform/start_reset_delete/index.ts new file mode 100644 index 0000000000000..14a9bcbc099c8 --- /dev/null +++ b/x-pack/test/functional_basic/apps/transform/start_reset_delete/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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('transform basic license', function () { + this.tags(['skipFirefox', 'transform']); + + // The transform UI should work the same as with a trial license + loadTestFile(require.resolve('../../../../functional/apps/transform/start_reset_delete')); + }); +} diff --git a/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts b/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts index 127e09a215daf..853d568f89d96 100644 --- a/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts +++ b/x-pack/test/functional_with_es_ssl/apps/discover/search_source_alert.ts @@ -306,9 +306,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await titleElem.getAttribute('value')).to.equal(dataView); }; - // Failing: See https://github.com/elastic/kibana/issues/148388 - // Failing: See https://github.com/elastic/kibana/issues/148386 - describe.skip('Search source Alert', () => { + describe('Search source Alert', () => { before(async () => { await security.testUser.setRoles(['discover_alert']); @@ -412,9 +410,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.click('openEditRuleFlyoutButton'); await queryBar.setQuery('message:msg-1'); await filterBar.addFilter({ field: 'message.keyword', operation: 'is', value: 'msg-1' }); - await retry.waitFor('filters modal to become hidden', async () => { - return !(await testSubjects.exists('saveFilter')); - }); await testSubjects.click('thresholdPopover'); await testSubjects.setValue('alertThresholdInput', '1'); diff --git a/x-pack/test/localization/tests/index.ts b/x-pack/test/localization/tests/index.ts index a5c844ed24843..812d8d546b614 100644 --- a/x-pack/test/localization/tests/index.ts +++ b/x-pack/test/localization/tests/index.ts @@ -10,5 +10,6 @@ import type { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile }: FtrProviderContext) { describe('Sanity checks', () => { loadTestFile(require.resolve('./login_page')); + loadTestFile(require.resolve('./lens')); }); } diff --git a/x-pack/test/localization/tests/lens/formula.ts b/x-pack/test/localization/tests/lens/formula.ts new file mode 100644 index 0000000000000..3a260e1becbe1 --- /dev/null +++ b/x-pack/test/localization/tests/lens/formula.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; 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']); + const elasticChart = getService('elasticChart'); + + describe('lens formula tests', () => { + it('should allow creation of a lens chart via formula', 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: 'formula', + formula: `count() + average(bytes)`, + }); + + expect(await PageObjects.lens.getWorkspaceErrorCount()).to.eql(0); + const data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data).to.be.ok(); + }); + }); +} diff --git a/x-pack/test/localization/tests/lens/index.ts b/x-pack/test/localization/tests/lens/index.ts new file mode 100644 index 0000000000000..0f4c210357833 --- /dev/null +++ b/x-pack/test/localization/tests/lens/index.ts @@ -0,0 +1,77 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { + const PageObjects = getPageObjects(['timePicker']); + const browser = getService('browser'); + const config = getService('config'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + let remoteEsArchiver; + + describe('lens app', () => { + const esArchive = 'x-pack/test/functional/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/lens_basic.json', + lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/test/functional/fixtures/kbn_archiver/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./smokescreen')); + loadTestFile(require.resolve('./formula')); + }); +} diff --git a/x-pack/test/localization/tests/lens/smokescreen.ts b/x-pack/test/localization/tests/lens/smokescreen.ts new file mode 100644 index 0000000000000..3414cc89bba65 --- /dev/null +++ b/x-pack/test/localization/tests/lens/smokescreen.ts @@ -0,0 +1,881 @@ +/* + * 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 { range } from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { getI18nLocaleFromServerArgs } from '../utils'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const find = getService('find'); + const listingTable = getService('listingTable'); + const testSubjects = getService('testSubjects'); + const elasticChart = getService('elasticChart'); + const filterBar = getService('filterBar'); + const retry = getService('retry'); + const config = getService('config'); + + function getTranslationFr(term: string) { + switch (term) { + case 'legacyMetric': + return 'Ancien indicateur'; + case 'datatable': + return 'Tableau'; + case 'bar': + return 'Vertical à barres'; + case 'bar_stacked': + return 'Vertical à barres empilées'; + case 'line': + return 'Ligne'; + case 'donut': + return 'Graphique en anneau'; + case 'pie': + return 'Camembert'; + case 'treemap': + return 'Compartimentage'; + case 'heatmap': + return 'Carte thermique'; + case 'Percent': + return 'Pourcent'; + case 'Number': + return 'Nombre'; + case 'Linear': + return 'Linéaire'; + case 'Records': + return 'Enregistrements'; + case 'records': + return 'enregistrements'; + case 'moving_average': + return 'Moyenne mobile de'; + case 'sum': + return 'somme'; + default: + return term; + } + } + + function getTranslationJa(term: string) { + switch (term) { + case 'legacyMetric': + return 'レガシーメトリック'; + case 'datatable': + return '表'; + case 'bar': + return '縦棒'; + case 'bar_stacked': + return '積み上げ縦棒'; + case 'line': + return '折れ線'; + case 'donut': + return 'ドーナッツ'; + case 'pie': + return '円'; + case 'treemap': + return 'ツリーマップ'; + case 'heatmap': + return 'ヒートマップ'; + case 'Number': + return '数字'; + case 'Percent': + return '割合(%)'; + case 'Linear': + return '線形'; + case 'Records': + case 'records': + return '記録'; + case 'moving_average': + return 'の移動平均'; + case 'sum': + return '合計'; + default: + return term; + } + } + + function getTranslationZh(term: string) { + switch (term) { + case 'legacyMetric': + return '旧版指标'; + case 'datatable': + return '表'; + case 'bar': + return '垂直条形图'; + case 'bar_stacked': + return '垂直堆积条形图'; + case 'line': + return '折线图'; + case 'donut': + return '圆环图'; + case 'pie': + return '饼图'; + case 'treemap': + return '树状图'; + case 'heatmap': + return '热图'; + case 'Number': + return '数字'; + case 'Percent': + return '百分比'; + case 'Linear': + return '线性'; + case 'Records': + case 'records': + return '记录'; + case 'moving_average': + return '的移动平均值'; + case 'sum': + return '求和'; + default: + return term; + } + } + + function getExpectedI18nTranslator(locale: string): (chartType: string) => string { + switch (locale) { + case 'ja-JP': + return getTranslationJa; + case 'zh-CN': + return getTranslationZh; + case 'fr-FR': + return getTranslationFr; + default: + return (v: string) => v; + } + } + + describe('lens smokescreen tests', () => { + let termTranslator: (chartType: string) => string; + + before(async () => { + const serverArgs: string[] = config.get('kbnTestServer.serverArgs'); + const kbnServerLocale = getI18nLocaleFromServerArgs(serverArgs); + termTranslator = getExpectedI18nTranslator(kbnServerLocale); + }); + + it('should allow creation of lens xy chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: '@message.raw', + }); + + await PageObjects.lens.switchToVisualization('lnsDatatable', termTranslator('datatable')); + await PageObjects.lens.removeDimension('lnsDatatable_rows'); + await PageObjects.lens.switchToVisualization('bar_stacked', termTranslator('bar_stacked')); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'ip', + }); + + await PageObjects.lens.save('Afancilenstest'); + + // Ensure the visualization shows up in the visualize list, and takes + // us back to the visualization as we configured it. + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('Afancilenstest'); + await PageObjects.lens.clickVisualizeListItemTitle('Afancilenstest'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.waitForVisualization('xyVisChart'); + + expect(await PageObjects.lens.getTitle()).to.eql('Afancilenstest'); + + // .echLegendItem__title is the only viable way of getting the xy chart's + // legend item(s), so we're using a class selector here. + // 4th item is the other bucket + expect(await find.allByCssSelector('.echLegendItem')).to.have.length(4); + }); + + it('should create an xy visualization with filters aggregation', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + // Change the IP field to filters + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-dimensionTrigger', + operation: 'filters', + keepOpen: true, + }); + await PageObjects.lens.addFilterToAgg(`geo.src : CN`); + await PageObjects.lens.waitForVisualization('xyVisChart'); + + // Verify that the field was persisted from the transition + expect(await PageObjects.lens.getFiltersAggLabels()).to.eql([`ip : *`, `geo.src : CN`]); + expect(await find.allByCssSelector('.echLegendItem')).to.have.length(2); + }); + + it('should transition from metric to table to metric', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('Artistpreviouslyknownaslens'); + await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.assertLegacyMetric('Maximum of bytes', '19,986'); + await PageObjects.lens.switchToVisualization('lnsDatatable', termTranslator('datatable')); + expect(await PageObjects.lens.getDatatableHeaderText()).to.eql('Maximum of bytes'); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('19,986'); + await PageObjects.lens.switchToVisualization( + 'lnsLegacyMetric', + termTranslator('legacyMetric') + ); + await PageObjects.lens.assertLegacyMetric('Maximum of bytes', '19,986'); + }); + + it('should transition from a multi-layer stacked bar to a multi-layer line chart and correctly remove all layers', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.createLayer(); + + expect(await PageObjects.lens.hasChartSwitchWarning('line', termTranslator('line'))).to.eql( + false + ); + + await PageObjects.lens.switchToVisualization('line', termTranslator('line')); + await PageObjects.lens.configureDimension({ + dimension: 'lns-layerPanel-1 > lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lns-layerPanel-1 > lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'median', + field: 'bytes', + }); + + expect(await PageObjects.lens.getLayerCount()).to.eql(2); + await PageObjects.lens.removeLayer(); + await PageObjects.lens.removeLayer(); + await testSubjects.existOrFail('workspace-drag-drop-prompt'); + }); + + it('should edit settings of xy line chart', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + await testSubjects.click('lnsXY_splitDimensionPanel > indexPattern-dimension-remove'); + await PageObjects.lens.switchToVisualization('line', termTranslator('line')); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'max', + field: 'memory', + keepOpen: true, + }); + await PageObjects.lens.editDimensionLabel('Test of label'); + await PageObjects.lens.editDimensionFormat(termTranslator('Percent')); + await PageObjects.lens.editDimensionColor('#ff0000'); + await PageObjects.lens.openVisualOptions(); + + await PageObjects.lens.useCurvedLines(); + await PageObjects.lens.editMissingValues('Linear'); + + await PageObjects.lens.assertMissingValues(termTranslator('Linear')); + + await PageObjects.lens.openDimensionEditor('lnsXY_yDimensionPanel > lns-dimensionTrigger'); + await PageObjects.lens.assertColor('#ff0000'); + + await testSubjects.existOrFail('indexPattern-dimension-formatDecimals'); + + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Test of label' + ); + }); + + it('should not show static value tab for data layers', async () => { + await PageObjects.lens.openDimensionEditor('lnsXY_yDimensionPanel > lns-dimensionTrigger'); + // Quick functions and Formula tabs should be visible + expect(await testSubjects.exists('lens-dimensionTabs-quickFunctions')).to.eql(true); + expect(await testSubjects.exists('lens-dimensionTabs-formula')).to.eql(true); + // Static value tab should not be visible + expect(await testSubjects.exists('lens-dimensionTabs-static_value')).to.eql(false); + + await PageObjects.lens.closeDimensionEditor(); + }); + + it('should be able to add very long labels and still be able to remove a dimension', async () => { + await PageObjects.lens.openDimensionEditor('lnsXY_yDimensionPanel > lns-dimensionTrigger'); + const longLabel = + 'Veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryvery long label wrapping multiple lines'; + await PageObjects.lens.editDimensionLabel(longLabel); + await PageObjects.lens.waitForVisualization('xyVisChart'); + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + longLabel + ); + expect( + await testSubjects.isDisplayed('lnsXY_yDimensionPanel > indexPattern-dimension-remove') + ).to.equal(true); + await PageObjects.lens.removeDimension('lnsXY_yDimensionPanel'); + await testSubjects.missingOrFail('lnsXY_yDimensionPanel > lns-dimensionTrigger'); + }); + + it('should allow creation of a multi-axis chart and switching multiple times', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchToVisualization('bar', termTranslator('bar')); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.dest', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'unique_count', + field: 'bytes', + keepOpen: true, + }); + + await PageObjects.lens.changeAxisSide('right'); + let data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.axes?.y.length).to.eql(2); + expect(data?.axes?.y.some(({ position }) => position === 'right')).to.eql(true); + + await PageObjects.lens.changeAxisSide('left'); + data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.axes?.y.length).to.eql(1); + expect(data?.axes?.y.some(({ position }) => position === 'right')).to.eql(false); + + await PageObjects.lens.changeAxisSide('right'); + await PageObjects.lens.waitForVisualization('xyVisChart'); + + await PageObjects.lens.closeDimensionEditor(); + }); + + it('should show value labels on bar charts when enabled', async () => { + // enable value labels + await PageObjects.lens.openVisualOptions(); + await testSubjects.click('lns_valueLabels_inside'); + + // check for value labels + let data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.bars?.[0].labels).not.to.eql(0); + + // switch to stacked bar chart + await PageObjects.lens.switchToVisualization('bar_stacked', termTranslator('bar_stacked')); + + // check for value labels + data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.bars?.[0].labels).not.to.eql(0); + }); + + it('should override axis title', async () => { + const axisTitle = 'overridden axis'; + await PageObjects.lens.toggleToolbarPopover('lnsLeftAxisButton'); + await testSubjects.setValue('lnsyLeftAxisTitle', axisTitle, { + clearWithKeyboard: true, + }); + + let data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.axes?.y?.[1].title).to.eql(axisTitle); + + // hide the gridlines + await testSubjects.click('lnsshowyLeftAxisGridlines'); + + data = await PageObjects.lens.getCurrentChartDebugState('xyVisChart'); + expect(data?.axes?.y?.[1].gridlines.length).to.eql(0); + }); + + it('should transition from line chart to donut chart and to bar chart', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + expect(await PageObjects.lens.hasChartSwitchWarning('donut', termTranslator('donut'))).to.eql( + true + ); + await PageObjects.lens.switchToVisualization('donut', termTranslator('donut')); + + expect(await PageObjects.lens.getTitle()).to.eql('lnsXYvis'); + expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sliceByDimensionPanel')).to.eql( + 'Top values of ip' + ); + expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sizeByDimensionPanel')).to.eql( + 'Average of bytes' + ); + + expect(await PageObjects.lens.hasChartSwitchWarning('bar', termTranslator('bar'))).to.eql( + false + ); + await PageObjects.lens.switchToVisualization('bar', termTranslator('bar')); + expect(await PageObjects.lens.getTitle()).to.eql('lnsXYvis'); + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel')).to.eql( + 'Top values of ip' + ); + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Average of bytes' + ); + }); + + it('should transition from bar chart to line chart using layer chart switch', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchLayerSeriesType('line'); + expect(await PageObjects.lens.getTitle()).to.eql('lnsXYvis'); + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel')).to.eql( + '@timestamp' + ); + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Average of bytes' + ); + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_splitDimensionPanel')).to.eql( + 'Top values of ip' + ); + }); + + it('should transition from pie chart to treemap chart', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsPieVis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsPieVis'); + await PageObjects.lens.goToTimeRange(); + expect( + await PageObjects.lens.hasChartSwitchWarning('treemap', termTranslator('treemap')) + ).to.eql(false); + await PageObjects.lens.switchToVisualization('treemap', termTranslator('treemap')); + expect( + await PageObjects.lens.getDimensionTriggersTexts('lnsPie_groupByDimensionPanel') + ).to.eql(['Top values of geo.dest', 'Top values of geo.src']); + expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sizeByDimensionPanel')).to.eql( + 'Average of bytes' + ); + }); + + it('should create a pie chart and switch to datatable', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchToVisualization('pie', termTranslator('pie')); + await PageObjects.lens.configureDimension({ + dimension: 'lnsPie_sliceByDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + disableEmptyRows: true, + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsPie_sizeByDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + expect( + await PageObjects.lens.hasChartSwitchWarning('lnsDatatable', termTranslator('datatable')) + ).to.eql(false); + await PageObjects.lens.switchToVisualization('lnsDatatable', termTranslator('datatable')); + + // Need to provide a fn for these + // expect(await PageObjects.lens.getDatatableHeaderText()).to.eql('@timestamp per 3 hours'); + // expect(await PageObjects.lens.getDatatableHeaderText(1)).to.eql('Average of bytes'); + expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('2015-09-20 00:00'); + expect(await PageObjects.lens.getDatatableCellText(0, 1)).to.eql('6,011.351'); + }); + + it('should create a heatmap chart and transition to barchart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchToVisualization('heatmap', termTranslator('heatmap')); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsHeatmap_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsHeatmap_yDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.dest', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsHeatmap_cellPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + expect(await PageObjects.lens.hasChartSwitchWarning('bar', termTranslator('bar'))).to.eql( + false + ); + await PageObjects.lens.switchToVisualization('bar', termTranslator('bar')); + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel')).to.eql( + '@timestamp' + ); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.contain( + 'bytes' + ); + }); + + it('should create a valid XY chart with references', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'moving_average', + keepOpen: true, + }); + await PageObjects.lens.configureReference({ + operation: termTranslator('sum'), + field: 'bytes', + }); + await PageObjects.lens.closeDimensionEditor(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'cumulative_sum', + keepOpen: true, + }); + await PageObjects.lens.configureReference({ + field: termTranslator('Records'), + }); + await PageObjects.lens.closeDimensionEditor(); + + // Two Y axes that are both valid + expect(await find.allByCssSelector('.echLegendItem')).to.have.length(2); + }); + + it('should allow formatting on references', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchToVisualization('lnsDatatable', termTranslator('datatable')); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsDatatable_rows > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + disableEmptyRows: true, + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsDatatable_metrics > lns-empty-dimension', + operation: 'moving_average', + keepOpen: true, + }); + await PageObjects.lens.configureReference({ + operation: termTranslator('sum'), + field: 'bytes', + }); + await PageObjects.lens.editDimensionFormat(termTranslator('Number')); + await PageObjects.lens.closeDimensionEditor(); + + await PageObjects.lens.waitForVisualization(); + + const values = await Promise.all( + range(0, 6).map((index) => PageObjects.lens.getDatatableCellText(index, 1)) + ); + expect(values).to.eql([ + '-', + '222,420.00', + '702,050.00', + '1,879,613.33', + '3,482,256.25', + '4,359,953.00', + ]); + }); + + /** + * The edge cases are: + * + * 1. Showing errors when creating a partial configuration + * 2. Being able to drag in a new field while in partial config + * 3. Being able to switch charts while in partial config + */ + it('should handle edge cases in reference-based operations', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'cumulative_sum', + }); + expect(await PageObjects.lens.getWorkspaceErrorCount()).to.eql(1); + + await PageObjects.lens.removeDimension('lnsXY_xDimensionPanel'); + expect(await PageObjects.lens.getWorkspaceErrorCount()).to.eql(2); + + await PageObjects.lens.dragFieldToDimensionTrigger( + '@timestamp', + 'lnsXY_xDimensionPanel > lns-empty-dimension' + ); + expect(await PageObjects.lens.getWorkspaceErrorCount()).to.eql(1); + + expect( + await PageObjects.lens.hasChartSwitchWarning('lnsDatatable', termTranslator('datatable')) + ).to.eql(false); + await PageObjects.lens.switchToVisualization('lnsDatatable', termTranslator('datatable')); + + // TODO: fix this later on + // expect(await PageObjects.lens.getDimensionTriggerText('lnsDatatable_metrics')).to.eql( + // 'Cumulative sum of (incomplete)' + // ); + }); + + it('should keep the field selection while transitioning to every reference-based operation', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'counter_rate', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'cumulative_sum', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'differences', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'moving_average', + }); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.contain( + 'bytes' + ); + }); + + it('should not leave an incomplete column in the visualization config with field-based operation', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + }); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + undefined + ); + }); + + it('should revert to previous configuration and not leave an incomplete column in the visualization config with reference-based operations', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'moving_average', + field: termTranslator('Records'), + }); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.contain( + termTranslator('moving_average') + ); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'median', + isPreviousIncompatible: true, + keepOpen: true, + }); + + expect(await PageObjects.lens.isDimensionEditorOpen()).to.eql(true); + + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.contain( + termTranslator('moving_average') + ); + }); + + it('should transition from unique count to last value', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'unique_count', + field: 'ip', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'last_value', + field: 'bytes', + isPreviousIncompatible: true, + }); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.contain( + 'bytes' + ); + }); + + it('should allow to change index pattern', async () => { + let indexPatternString; + if (config.get('esTestCluster.ccs')) { + indexPatternString = 'ftr-remote:log*'; + } else { + indexPatternString = 'log*'; + } + await PageObjects.lens.switchFirstLayerIndexPattern(indexPatternString); + expect(await PageObjects.lens.getFirstLayerIndexPattern()).to.equal(indexPatternString); + }); + + it('should allow filtering by legend on an xy chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'extension.raw', + }); + + await PageObjects.lens.filterLegend('jpg'); + const hasExtensionFilter = await filterBar.hasFilter('extension.raw', 'jpg'); + expect(hasExtensionFilter).to.be(true); + + await filterBar.removeFilter('extension.raw'); + }); + + it('should allow filtering by legend on a pie chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.switchToVisualization('pie', termTranslator('pie')); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsPie_sliceByDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'extension.raw', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsPie_sliceByDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'agent.raw', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsPie_sizeByDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.filterLegend('jpg'); + const hasExtensionFilter = await filterBar.hasFilter('extension.raw', 'jpg'); + expect(hasExtensionFilter).to.be(true); + + await filterBar.removeFilter('extension.raw'); + }); + + it('should show visual options button group for a donut chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.switchToVisualization('donut', termTranslator('donut')); + + const hasVisualOptionsButton = await PageObjects.lens.hasVisualOptionsButton(); + expect(hasVisualOptionsButton).to.be(true); + + await PageObjects.lens.openVisualOptions(); + await retry.try(async () => { + expect(await PageObjects.lens.hasEmptySizeRatioButtonGroup()).to.be(true); + }); + }); + + it('should not show visual options button group for a pie chart', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.switchToVisualization('pie', termTranslator('pie')); + + const hasVisualOptionsButton = await PageObjects.lens.hasVisualOptionsButton(); + expect(hasVisualOptionsButton).to.be(false); + }); + }); +} diff --git a/x-pack/test/localization/tests/login_page.ts b/x-pack/test/localization/tests/login_page.ts index 6ff2b3a9b4757..613720601d243 100644 --- a/x-pack/test/localization/tests/login_page.ts +++ b/x-pack/test/localization/tests/login_page.ts @@ -7,6 +7,7 @@ import expect from '@kbn/expect'; import type { FtrProviderContext } from '../ftr_provider_context'; +import { getI18nLocaleFromServerArgs } from './utils'; /** * Strings Needs to be hardcoded since getting it from the i18n.translate @@ -28,19 +29,6 @@ function getExpectedI18nTranslation(locale: string): string | undefined { } } -function getI18nLocaleFromServerArgs(kbnServerArgs: string[]): string { - const re = /--i18n\.locale=(?.*)/; - for (const serverArg of kbnServerArgs) { - const match = re.exec(serverArg); - const locale = match?.groups?.locale; - if (locale) { - return locale; - } - } - - throw Error('i18n.locale is not set in the server arguments'); -} - export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const config = getService('config'); diff --git a/x-pack/test/localization/tests/utils.ts b/x-pack/test/localization/tests/utils.ts new file mode 100644 index 0000000000000..4ac2a970ef02c --- /dev/null +++ b/x-pack/test/localization/tests/utils.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function getI18nLocaleFromServerArgs(kbnServerArgs: string[]): string { + const re = /--i18n\.locale=(?.*)/; + for (const serverArg of kbnServerArgs) { + const match = re.exec(serverArg); + const locale = match?.groups?.locale; + if (locale) { + return locale; + } + } + + throw Error('i18n.locale is not set in the server arguments'); +} diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index f730e58ef136e..94173300eeef5 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -60,6 +60,7 @@ export default function ({ getService }: FtrProviderContext) { 'actions:.swimlane', 'actions:.teams', 'actions:.tines', + 'actions:.torq', 'actions:.webhook', 'actions:.xmatters', 'actions_telemetry', diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/connector_types.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/connector_types.ts new file mode 100644 index 0000000000000..16eb4f7d7a21c --- /dev/null +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/connector_types.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const commonScreenshots = getService('commonScreenshots'); + const screenshotDirectories = ['response_ops_docs', 'stack_alerting']; + const pageObjects = getPageObjects(['common', 'header']); + const actions = getService('actions'); + const testSubjects = getService('testSubjects'); + + describe('connector types', function () { + beforeEach(async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('serverlog connector screenshot', async () => { + await pageObjects.common.navigateToApp('connectors'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await actions.common.openNewConnectorForm('server-log'); + await testSubjects.setValue('nameInput', 'Server log test connector'); + await commonScreenshots.takeScreenshot('serverlog-connector', screenshotDirectories); + const saveTestButton = await testSubjects.find('create-connector-flyout-save-test-btn'); + await saveTestButton.click(); + await commonScreenshots.takeScreenshot('serverlog-params-test', screenshotDirectories); + const flyOutCancelButton = await testSubjects.find('euiFlyoutCloseButton'); + await flyOutCancelButton.click(); + }); + }); +} diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts index 200a3ae1e8793..85e118756c4b7 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/index.ts @@ -7,8 +7,26 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const browser = getService('browser'); + const actions = getService('actions'); + describe('stack alerting', function () { + before(async () => { + await browser.setWindowSize(1920, 1080); + await actions.api.createConnector({ + name: 'server-log-connector', + config: {}, + secrets: {}, + connectorTypeId: '.server-log', + }); + }); + + after(async () => { + await actions.api.deleteAllConnectors(); + }); + loadTestFile(require.resolve('./list_view')); + loadTestFile(require.resolve('./connector_types')); }); } diff --git a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts index cdad076a62ba3..2b7df0bfd6e48 100644 --- a/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts +++ b/x-pack/test/screenshot_creation/apps/response_ops_docs/stack_alerting/list_view.ts @@ -11,12 +11,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const commonScreenshots = getService('commonScreenshots'); const screenshotDirectories = ['response_ops_docs', 'stack_alerting']; const pageObjects = getPageObjects(['common', 'header']); - const actions = getService('actions'); const rules = getService('rules'); const testSubjects = getService('testSubjects'); describe('list view', function () { - let serverLogConnectorId: string; let ruleId: string; const indexThresholdRule = { consumer: 'alerts', @@ -38,13 +36,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; before(async () => { - const connectorName = `server-log-connector`; - ({ id: serverLogConnectorId } = await createServerLogConnector(connectorName)); ({ id: ruleId } = await rules.api.createRule(indexThresholdRule)); }); after(async () => { - await actions.api.deleteConnector(serverLogConnectorId); await rules.api.deleteRule(ruleId); }); @@ -87,13 +82,4 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await commonScreenshots.takeScreenshot('snooze-panel', screenshotDirectories, 1400, 1024); }); }); - - const createServerLogConnector = async (name: string) => { - return actions.api.createConnector({ - name, - config: {}, - secrets: {}, - connectorTypeId: '.server-log', - }); - }; } diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts index 1c947c3852446..deb4a53190ff6 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/index.ts @@ -13,8 +13,7 @@ export default function ({ loadTestFile, getService, getPageObjects }: FtrProvid const PageObjects = getPageObjects(['common']); const searchSessions = getService('searchSessions'); - // FLAKY: https://github.com/elastic/kibana/issues/103043 - describe.skip('Dashboard', function () { + describe('Dashboard', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.savedObjects.cleanStandardList(); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts index b9da2c5f67601..d5fbdadda74f4 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts @@ -13,6 +13,7 @@ import { BASE_POLICY_RESPONSE_ROUTE, ENDPOINTS_ACTION_LIST_ROUTE, GET_PROCESSES_ROUTE, + GET_FILE_ROUTE, HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, ISOLATE_HOST_ROUTE_V2, @@ -20,6 +21,7 @@ import { METADATA_TRANSFORMS_STATUS_ROUTE, SUSPEND_PROCESS_ROUTE, UNISOLATE_HOST_ROUTE_V2, + EXECUTE_ROUTE, } from '@kbn/security-solution-plugin/common/endpoint/constants'; import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -113,6 +115,22 @@ export default function ({ getService }: FtrProviderContext) { }, ]; + const canWriteFileOperationsApiList: ApiCallsInterface[] = [ + { + method: 'post', + path: GET_FILE_ROUTE, + body: { endpoint_ids: ['one'], parameters: { path: '/opt/file/doc.txt' } }, + }, + ]; + + const canWriteExecuteOperationsApiList: ApiCallsInterface[] = [ + { + method: 'post', + path: EXECUTE_ROUTE, + body: { endpoint_ids: ['one'], parameters: { command: 'ls -la' } }, + }, + ]; + const superuserApiList: ApiCallsInterface[] = [ { method: 'get', @@ -251,6 +269,8 @@ export default function ({ getService }: FtrProviderContext) { ...canReadActionsLogManagementApiList, ...canIsolateHostApiList, ...canWriteProcessOperationsApiList, + ...canWriteExecuteOperationsApiList, + ...canWriteFileOperationsApiList, ...superuserApiList, ]) { it(`should return 200 when [${apiListItem.method.toUpperCase()} ${ diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts new file mode 100644 index 0000000000000..413c8bd69e828 --- /dev/null +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_response_actions/execute.ts @@ -0,0 +1,144 @@ +/* + * 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 { wrapErrorAndRejectPromise } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/utils'; +import expect from '@kbn/expect'; +import { EXECUTE_ROUTE } from '@kbn/security-solution-plugin/common/endpoint/constants'; +import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { ROLE } from '../../services/roles_users'; + +export default function ({ getService }: FtrProviderContext) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const endpointTestResources = getService('endpointTestResources'); + + describe('Endpoint `execute` response action', () => { + let indexedData: IndexedHostsAndAlertsResponse; + let agentId = ''; + + before(async () => { + indexedData = await endpointTestResources.loadEndpointData(); + agentId = indexedData.hosts[0].agent.id; + }); + + after(() => { + endpointTestResources.unloadEndpointData(indexedData); + }); + + it('should not allow `execute` action without required privilege', async () => { + await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.t1_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } }) + .expect(403, { + statusCode: 403, + error: 'Forbidden', + message: 'Endpoint authorization failure', + }) + .catch(wrapErrorAndRejectPromise); + }); + + it('should error on invalid endpoint id', async () => { + await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [' '], parameters: { command: 'ls -la' } }) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: '[request body.endpoint_ids]: endpoint_ids cannot contain empty strings', + }); + }); + + it('should error on missing endpoint id', async () => { + await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ parameters: { command: 'ls -la' } }) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: + '[request body.endpoint_ids]: expected value of type [array] but got [undefined]', + }); + }); + + it('should error on invalid `command` parameter', async () => { + await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [agentId], parameters: { command: ' ' } }) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: '[request body.parameters.command]: command cannot be an empty string', + }); + }); + + it('should error on missing `command` parameter', async () => { + await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [agentId] }) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: + '[request body.parameters.command]: expected value of type [string] but got [undefined]', + }); + }); + + it('should error on invalid `timeout` parameter', async () => { + await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 'too' } }) + .expect(400, { + statusCode: 400, + error: 'Bad Request', + message: + '[request body.parameters.timeout]: expected value of type [number] but got [string]', + }); + }); + + it('should succeed with valid endpoint id and command', async () => { + const { + body: { data }, + } = await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la' } }) + .expect(200); + + expect(data.agents[0]).to.eql(agentId); + expect(data.command).to.eql('execute'); + expect(data.parameters.command).to.eql('ls -la'); + }); + + it('should succeed with valid endpoint id, command and an optional timeout', async () => { + const { + body: { data }, + } = await supertestWithoutAuth + .post(EXECUTE_ROUTE) + .auth(ROLE.response_actions_role, 'changeme') + .set('kbn-xsrf', 'true') + .send({ endpoint_ids: [agentId], parameters: { command: 'ls -la', timeout: 2000 } }) + .expect(200); + + expect(data.agents[0]).to.eql(agentId); + expect(data.command).to.eql('execute'); + expect(data.parameters.command).to.eql('ls -la'); + expect(data.parameters.timeout).to.eql(2000); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index f6530a76a18ab..fbd33d38d1a94 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -47,6 +47,7 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider loadTestFile(require.resolve('./policy')); loadTestFile(require.resolve('./package')); loadTestFile(require.resolve('./endpoint_authz')); + loadTestFile(require.resolve('./endpoint_response_actions/execute')); loadTestFile(require.resolve('./file_upload_index')); loadTestFile(require.resolve('./endpoint_artifacts/trusted_apps')); loadTestFile(require.resolve('./endpoint_artifacts/event_filters')); diff --git a/x-pack/test/security_solution_endpoint_api_int/config.ts b/x-pack/test/security_solution_endpoint_api_int/config.ts index 6e0d777c8d3ac..aaae0787d3c82 100644 --- a/x-pack/test/security_solution_endpoint_api_int/config.ts +++ b/x-pack/test/security_solution_endpoint_api_int/config.ts @@ -32,7 +32,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // this will be removed in 8.7 when the file upload feature is released `--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`, // this will be removed in 8.7 when the artifacts RBAC is released - `--xpack.securitySolution.enableExperimental=${JSON.stringify(['endpointRbacEnabled'])}`, + `--xpack.securitySolution.enableExperimental=${JSON.stringify([ + 'endpointRbacEnabled', + 'responseActionGetFileEnabled', + 'responseActionExecuteEnabled', + ])}`, ], }, }; diff --git a/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts b/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts index 365a00f804289..552744510ab59 100644 --- a/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts +++ b/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts @@ -15,6 +15,7 @@ import { getSocManager } from '@kbn/security-solution-plugin/scripts/endpoint/co import { getPlatformEngineer } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/platform_engineer'; import { getEndpointOperationsAnalyst } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_operations_analyst'; import { getEndpointSecurityPolicyManager } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/endpoint_security_policy_manager'; +import { getWithResponseActionsRole } from '@kbn/security-solution-plugin/scripts/endpoint/common/roles_users/with_response_actions_role'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -28,6 +29,7 @@ export enum ROLE { platform_engineer = 'platformEngineer', endpoint_operations_analyst = 'endpointOperationsAnalyst', endpoint_security_policy_manager = 'endpointSecurityPolicyManager', + response_actions_role = 'executeResponseActions', } const rolesMapping: { [id: string]: Omit } = { @@ -39,6 +41,7 @@ const rolesMapping: { [id: string]: Omit } = { platformEngineer: getPlatformEngineer(), endpointOperationsAnalyst: getEndpointOperationsAnalyst(), endpointSecurityPolicyManager: getEndpointSecurityPolicyManager(), + executeResponseActions: getWithResponseActionsRole(), }; export function RolesUsersProvider({ getService }: FtrProviderContext) { diff --git a/yarn.lock b/yarn.lock index ecab149308449..9ef3b9b506ee4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6244,26 +6244,26 @@ dependencies: remove-accents "0.4.2" -"@tanstack/query-core@4.22.0": - version "4.22.0" - resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.22.0.tgz#7a786fcea64e229ed5d4308093dd644cdfaa895e" - integrity sha512-OeLyBKBQoT265f5G9biReijeP8mBxNFwY7ZUu1dKL+YzqpG5q5z7J/N1eT8aWyKuhyDTiUHuKm5l+oIVzbtrjw== +"@tanstack/query-core@4.24.4": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.24.4.tgz#6fe78777286fdd805ac319c7c743df4935e18ee2" + integrity sha512-9dqjv9eeB6VHN7lD3cLo16ZAjfjCsdXetSAD5+VyKqLUvcKTL0CklGQRJu+bWzdrS69R6Ea4UZo8obHYZnG6aA== -"@tanstack/react-query-devtools@^4.20.9": - version "4.22.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-4.22.0.tgz#e39f04e55428ff6ce2b2796f3171db170afcb2a8" - integrity sha512-YeYFBnfqvb+ZlA0IiJqiHNNSzepNhI1p2o9i8NlhQli9+Zrn230M47OBaBUs8qr3DD1dC2zGB1Dis50Ktz8gAA== +"@tanstack/react-query-devtools@^4.23.0": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-4.24.4.tgz#5f0bd45f929950ff2b56a1a3ed18a006780e3495" + integrity sha512-4mldcR99QDX8k94I+STM9gPsYF+FDAD2EQJvHtxR2HrDNegbfmY474xuW0QUZaNW/vJi09Gak6b6Vy2INWhL6w== dependencies: "@tanstack/match-sorter-utils" "^8.7.0" superjson "^1.10.0" use-sync-external-store "^1.2.0" -"@tanstack/react-query@^4.20.9": - version "4.22.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.22.0.tgz#aaa4b41a6d306be6958018c74a8a3bb3e9f1924c" - integrity sha512-P9o+HjG42uB/xHR6dMsJaPhtZydSe4v0xdG5G/cEj1oHZAXelMlm67/rYJNQGKgBamKElKogj+HYGF+NY2yHYg== +"@tanstack/react-query@^4.23.0": + version "4.24.4" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.24.4.tgz#79e892edac33d8aa394795390c0f79e4a8c9be4d" + integrity sha512-RpaS/3T/a3pHuZJbIAzAYRu+1nkp+/enr9hfRXDS/mojwx567UiMksoqW4wUFWlwIvWTXyhot2nbIipTKEg55Q== dependencies: - "@tanstack/query-core" "4.22.0" + "@tanstack/query-core" "4.24.4" use-sync-external-store "^1.2.0" "@testim/chrome-version@^1.1.3": @@ -22954,10 +22954,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.41.3: - version "7.41.5" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.41.5.tgz#dcd0e7438c15044eadc99df6deb889da5858a03b" - integrity sha512-DAKjSJ7X9f16oQrP3TW2/eD9N6HOgrmIahP4LOdFphEWVfGZ2LulFd6f6AQ/YS/0cx/5oc4j8a1PXxuaurWp/Q== +react-hook-form@^7.42.1: + version "7.43.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.43.0.tgz#d0c19b5c4ec561fbf8d652869ccb513c11c772e7" + integrity sha512-/rVEz7T0gLdSFwPqutJ1kn2e0sQNyb9ci/hmwEYr2YG0KF/LSuRLvNrf9QWJM+gj88CjDpDW5Bh/1AD7B2+z9Q== react-input-autosize@^3.0.0: version "3.0.0"